Settings and Storage like Ionic's "super" template on App (Lazy Load)

Hi,

I am trying to follow Ionic cli’s “super” template on an app that uses lazy load.

However I don’t need to lazy load the settings that use ionic storage (https://ionicframework.com/docs/storage/).

When using the storage module directly everything works fine, however when using it inside a settings.ts provider just like the ionic “super” template (so I can add some business logic to populate the settings from an http call when the app starts), this is when things stop working.

Here is my app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { HttpModule } from "@angular/http";
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { IonicStorageModule } from '@ionic/storage';

import { MyApp } from './app.component';
import { SettingsProvider } from './../providers/settings/settings';

import { StatusBar } from '@ionic-native/status-bar';
import { SplashScreen } from '@ionic-native/splash-screen';

export function provideSettings(storage: Storage) {
  return new SettingsProvider(storage, {
    Testkey: 'Hello Settings'
  });
}

@NgModule({
  declarations: [
    MyApp
  ],
  imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp),
    IonicStorageModule.forRoot()
  ],
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp
  ],
  providers: [
    StatusBar,
    SplashScreen,
    { provide: ErrorHandler, useClass: IonicErrorHandler },
    { provide: SettingsProvider, useFactory: provideSettings, deps: [Storage] },
  ]
})
export class AppModule {}

and the settings provider is the exact replica from the “super” template, just changed the name to settingsProvider instead of settings:

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';

/**
 * A simple settings/config class for storing key/value pairs with persistence.
 */
@Injectable()
export class SettingsProvider {
  private SETTINGS_KEY: string = '_settings';

  settings: any;

  _defaults: any;
  _readyPromise: Promise<any>;

  constructor(public storage: Storage, defaults: any) {
    this._defaults = defaults;
  }

  load() {
    return this.storage.get(this.SETTINGS_KEY).then((value) => {
      if (value) {
        this.settings = value;
        return this._mergeDefaults(this._defaults);
      } else {
        return this.setAll(this._defaults).then((val) => { this.settings = val; })
      }
    });
  }

  _mergeDefaults(defaults: any) {
    for (let defaultSettings in defaults) {
      if (!(defaultSettings in this.settings)) {
        this.settings[defaultSettings] = defaults[defaultSettings];
      }
    }
    return this.setAll(this.settings);
  }

  merge(settings: any) {
    for (let setting in settings) {
      this.settings[setting] = settings[setting];
    }
    return this.save();
  }

  setValue(key: string, value: any) {
    this.settings[key] = value;
    return this.storage.set(this.SETTINGS_KEY, this.settings);
  }

  setAll(value: any) {
    return this.storage.set(this.SETTINGS_KEY, value);
  }

  getValue<T>(key: string): Promise<T> {
    return this.storage.get(this.SETTINGS_KEY).then(settings => { return settings[key] as T; });
  }

  save() {
    return this.setAll(this.settings);
  }

  get allSettings() {
    return this.settings;
  }
}

then I’d inject it to a page like so:

...
import { SettingsProvider } from './../../providers/settings/settings';
@IonicPage({
  name: 'MyPage'
})
@Component({
  selector: 'my-page',
  templateUrl: 'mypage.html'
})
export class MyPage {
  items: Array<Item>;

  constructor(public navCtrl: NavController, private api: ApiProvider, private settings: SettingsProvider) {
    this.api.get<Array<Item>>("items.json").subscribe(response => this.items = response);
    this.settings.load().then(() => {
      this.settings.setValue("Testkey", "TestValue");
      this.settings.getValue('Testkey').then(val => {
        console.log(val);
      });
    });
  }
...
}

I get this error on lazy loaded pages:

Runtime Error
Uncaught (in promise): Error: No provider for Storage! Error: No provider for Storage! at injectionError (http://localhost:8100/build/vendor.js:1590:86) at noProviderError (http://localhost:8100/build/vendor.js:1628:12) at ReflectiveInjector_._throwOrNull (http://localhost:8100/build/vendor.js:3129:19) at ReflectiveInjector_._getByKeyDefault (http://localhost:8100/build/vendor.js:3168:25) at ReflectiveInjector_._getByKey (http://localhost:8100/build/vendor.js:3100:25) at ReflectiveInjector_.get (http://localhost:8100/build/vendor.js:2969:21) at AppModuleInjector.get (ng:///AppModule/module.ngfactory.js:252:111) at AppModuleInjector.getInternal (ng:///AppModule/module.ngfactory.js:400:58) at AppModuleInjector.NgModuleInjector.get (http://localhost:8100/build/vendor.js:3936:44) at HomePageModuleInjector.NgModuleInjector.get (http://localhost:8100/build/vendor.js:3937:52)

Ionic Framework: 3.6.1
Ionic App Scripts: 2.1.3
Angular Core: 4.1.3
Angular Compiler CLI: 4.1.3
Node: 8.2.1
OS Platform: Windows 10
Navigator Platform: Win32
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36

I tried many things like adding the settings provider it to the lazy loading page module (which I think is wrong), which didn’t work, since it’s already loaded in the app module.
Also tried just adding it like a normal provider:
this:

providers: [
    ...
    SettingsProvider,
  ]

instead of this

providers: [
    ...
    { provide: SettingsProvider, useFactory: provideSettings, deps: [Storage] },
  ]

also didn’t work.

I am fairly new to Ionic, I would really appreciate any help or explanation.

Thanks

What does that mean? One lazy loads pages or files, not the usage of a library.

Let me try and explain it better :slight_smile:
I just meant I needed the settings provider (that uses ionic storage within) to have a shared instance in app module, so that I would inject that instance in any lazy loaded page I have,
I just don’t know the right way to do this.
I hope this clears things up.

Never mind, I found out the problem, It had nothing to do with lazy loading.

The app.module was missing an Import:

import { Storage } from '@ionic/storage';

also with this way of adding it to the provider:

providers: [
    ...
    { provide: SettingsProvider, useFactory: provideSettings, deps: [Storage] },
  ]

My code editor did not give an error since typescript already has an interface called Storage, so whenever I referred to Storage as a dependency in the providers array, it gets the typescript interface instead.

Thanks.

1 Like

I’m confused why we are making this so complicated. Why can’t you just add SettingsProvider to the providers array and let DI deal with feeding it a Storage?

1 Like