Need help resolving issue with Storage in Ionic 2 Application

Hello,

I am fairly new to Ionic 2. I like what I see so far but I have been having a problem injecting the Storage object into a service that I wrote.

Here is an excerpt from the Ripple log in the Developer tools of Chrome:
Unhandled Promise rejection: Error in ./MyApp class MyApp - inline template:0:0 caused by: No provider for Storage! ; Zone: ; Task: Promise.then ; Value: ViewWrappedError_nativeError: Error: Error in ./MyApp class MyApp - inline template:0:0 caused by: No provider for Storage!

I get the same thing when look at the logcat from adb I also get a similar error:
I/MultiDex(29845): VM has multidex support, MultiDex support library is disabled.

I/chromium(29041): [INFO:CONSOLE(48452)] “EXCEPTION: Error in ./MyApp class MyApp - inline template:0:0 caused by: No provider for Storage!”, source: file:///android_asset/www/build/main.js (48452)

I/chromium(29041): [INFO:CONSOLE(48454)] “ORIGINAL EXCEPTION: No provider for Storage!”, source: file:///android_asset/www/build/main.js (48454)

I/chromium(29041): [INFO:CONSOLE(48457)] “ORIGINAL STACKTRACE:”, source: file:///android_asset/www/build/main.js (48457)

I/chromium(29041): [INFO:CONSOLE(48458)] "Error: No provider for Storage!

I/chromium(29041): at NoProviderError.BaseError [as constructor] (file:///android_asset/www/build/main.js:7415:34)

I’ve looked around on google and checked out the articles written by polyglot-developer but those examples seem outdated since they reference things that were part of the Beta and are no longer.

Here are my dependencies from package.json:
“dependencies”: {
"@angular/common": “2.2.1”,
"@angular/compiler": “2.2.1”,
"@angular/compiler-cli": “2.2.1”,
"@angular/core": “2.2.1”,
"@angular/forms": “2.2.1”,
"@angular/http": “2.2.1”,
"@angular/platform-browser": “2.2.1”,
"@angular/platform-browser-dynamic": “2.2.1”,
"@angular/platform-server": “2.2.1”,
"@ionic/storage": “^1.1.6”,
“ionic-angular”: “2.0.0”,
“ionic-native”: “2.2.11”,
“ionicons”: “3.0.0”,
“rxjs”: “5.0.0-beta.12”,
“zone.js”: “0.6.26”
},
“devDependencies”: {
"@ionic/app-scripts": “1.0.0”,
“typescript”: “2.0.9”
},

Here is where I first reference it in my app.module.ts
import { NgModule, ErrorHandler } from ‘@angular/core’;
import { IonicApp, IonicModule, IonicErrorHandler} from ‘ionic-angular’;
import { MyApp } from ‘./app.component’;
import { LandingPage } from ‘…/pages/landing/landing’;
import { Storage } from ‘@ionic/storage’;
import { UserSettingService } from ‘…/services/UserSettingService’;

@NgModule({
declarations: [
MyApp,
LandingPage
],
imports: [
IonicModule.forRoot(MyApp)
],
bootstrap: [IonicApp],
entryComponents: [
MyApp,
LandingPage
],
providers: [
Storage,
//{ provide: Storage, useFactory: getStorage },
{ provide: ErrorHandler, useClass: IonicErrorHandler },
UserSettingService
]
})
export class AppModule {}

I have a service where I am trying to inject the storage:
export interface IUserSettingsService
{
retrieveUserSettings(
retrieveCallback: (settings: UserSettings) => void,
failCallback: (error: Error) => void
);

persistUserSettings(settings: UserSettings,         
    failCallback: (error: Error) => void
);

}

@Injectable()
export class UserSettingService implements IUserSettingsService
{
readonly USER_KEY: string = “user.settings”;

constructor(private storage: Storage) {        
}    

retrieveUserSettings(
    retrieveCallback: (settings: UserSettings) => void,
    failCallback: (error: Error) => void) {

    this.storage.get(this.USER_KEY)
        .then((val) => { retrieveCallback(val) })
        .catch((error) => { failCallback(error) });
}

persistUserSettings(settings: UserSettings, failCallback: (error: Error) => void) {
    this.storage.set(this.USER_KEY, settings)
        .catch((error) => { failCallback(error) });
}

}

And then a page where I am trying to inject my service:
@Component({
selector: ‘landing’,
templateUrl: ‘landing.html’,
providers: [UserSettingService]
})
export class LandingPage {

userSettings: UserSettings;

constructor(public navCtrl: NavController, settingsService: UserSettingService) {
    settingsService.retrieveUserSettings(this.onUserSettingsRetrieved, this.onUserSettingsFailed);
}

onUserSettingsRetrieved(userSettings: UserSettings) {
    /// some business logi
}

onUserSettingsFailed(err:Error) {                
    /// report some error message             
}

}

Please help.

I would start by losing this and the whole IUserSettingsService business.

I can try that as a smaller example… but I am curious as to why you say
that. It seems like having services and providers of services is a fairly
common Angular 2 practice. Does ionic 2 not use the same dependency
injection framework as Angular 2?

What is your reasoning?

As for my first recommendation, it’s because providers are declared in modules now (which you already did), not components. For the second, it’s because TypeScript interfaces are not first-class citizens, and in fact do not exist at all at runtime, so they are especially tricky when it comes to things like dependency injection magic. Things are much simpler and less error-prone if you are dealing only with concrete classes when it comes to DI.

Thanks Robert. That makes sense to me…
To expand on your answer and make sure that I understand (since I am new to this).
Basically Typescript interfaces are a compile time construct, they don’t get transpiled down to the actual javascript that will run.
Additionally if you need the flexibility of switching out an implementation you can use an object that has the same shape as the service you are trying to provide using concrete classes instead of declaring and trying to pass interfaces around. (not sure I have this last part right, but I can google it).

Additionally I tried doing a simple-stupid blank application that uses storage and the dependency injection succeeds.
Now in Ripple it basically prompts me for callbacks with the “I Has Cheezeburger” alerts since the cordova-sqllite-storage plugin isn’t supported within ripple.

Thanks for your help.

Basically, yes. I’m not sure I would use “can” there as much as “are generally better off just”. I expect (especially if you’re coming from a C# or Java background) that one would be tempted to use interfaces as you were here, and I’m certainly not saying that there’s anything inherently wrong with that approach. It’s just deceptively tricky in the world of TypeScript and DI. Look at all the kludgery needed in here when it comes to trying to inject off interfaces.

Guilty as charged… my background is Java and C#. Thanks for your help on this. I was able to get past my issues.

1 Like