Storage is always undefined


#1

Hi all,

I have a Project where I need to use the storage to store user data and accesstokens. I have written the following code and put it in auth-service.ts that is located in the providers directory:

public setAccessToken(accessToken: string) {
    console.log('Setting access token. Storage = ' + this.storage );

    this.storage.set('access_token', accessToken);
  }

No matter what, the storage is always undefined. I have import { Storage } from ‘@ionic/storage’; at the top of the provider file. In the constructor I have

public storage: Storage,

In the app.module.ts I have the following lines

imports: [
    BrowserModule,
    HttpModule,
    IonicModule.forRoot(MyApp),
    IonicStorageModule.forRoot()
  ],

In all the pages I inject the AuthService object into the constructor so that I can call auth.setAccessToken.

No matter what, everytime I call auth.setAccessToken I get an error that this.storage is undefined. How can I fix this?


#2

Okay, assuming that you correctly ran the install commands for storage, where are you calling setAccessToken? Perhaps inside a constructor in your service? It’s possible the storage isn’t ready when you’re trying to reach it.

You could wrap your call in a storage.ready() to prevent these errors.


#3

Thanks luukschoen,

I followed the instructions on the official website ( https://ionicframework.com/docs/storage/ )

I ran the command

npm install --save @ionic/storage

I have a login page and when the user presses the login button the login data is send to a microservice and when the authentication is correct I call auth.setAccessToken().

My login function looks like this:

public login() {
    this.showLoading();
    this.auth.login(this.loginData).subscribe(res => {
        if (res.access_token) {
          this.auth.setAccessToken(res.access_token)

          this.nav.setRoot('UserDashboardPage');
        } else {
          console.log("Access Denied");
        }
      },
      error => {
        console.log("Can not connect");
      });
  }

So storage is undefined long after everything has been initialized.


#4

I think what you’re doing (console.log this.storage) isn’t something very useful. If you want to now whether setting the token in your storage succeeded, use the Promise wrapper of storage.

public setAccessToken(accessToken: string) {
    return this.storage.set('access_token', accessToken);
  }

If you return the function, you can do the then inside your public login function:

public login() {
    this.showLoading();
    this.auth.login(this.loginData).subscribe(res => {
        if (res.access_token) {
          this.auth.setAccessToken(res.access_token).then(
          (result) => {
             console.log(result) // here's where my result can be logged, so whether my set succeeded or not
             }
         }

          this.nav.setRoot('UserDashboardPage');
        } else {
          console.log("Access Denied");
        }
      },
      error => {
        console.log("Can not connect");
      });
  }

You can also then within your service if you want to log the result or do something with it, depends on your usecase. If you want to look into your storage, check out your chrome inspecetor, --> application --> storage --> indexedDB (if you’re using chrome at least).

It’s weird though it logs undefined, since it should be something like this:

Storage {_driver: "asyncStorage", _dbPromise: t}
driver
:
(...)
_dbPromise
:
t
__zone_symbol__state
:
true
__zone_symbol__value
:
LocalForage
__proto__
:
Object
_driver
:
"asyncStorage"
__proto__
:
Object

By any chance you’re running some config with the storageProvider? Already tried what happened with a blank project?


#5

When I use chrome and I use the chrome inspector, I click on indexedDB and it’s empty. There is nothing in there. Also I have modified the Authservice so that I do an alert of the storage object being injected:

constructor(private http:Http, private storage: Storage) {
  alert('Storage = ' + storage);
}

And the alert says ‘Storage = undefined’. In the imports part of the app module I use the following:

BrowserModule,
HttpModule,
IonicModule.forRoot(MyApp),
IonicStorageModule.forRoot()

So no special configuration.


#6

what about wrapping it in storage.ready ? Could you post the contents of your package.json and ionic info over here?


#7

I then get an error that ready() can not be invoked on an undefined object. I have also tried NativeStorage and Storage. Both are null and aren’t injected.

My package.json

{
    "name": "balans-app",
    "version": "0.0.1",
    "author": "Me",
    "homepage": "http://www.ihavenowebsiteyet.com/",
    "private": true,
    "scripts": {
        "clean": "ionic-app-scripts clean",
        "build": "ionic-app-scripts build",
        "lint": "ionic-app-scripts lint",
        "ionic:build": "ionic-app-scripts build",
        "ionic:serve": "ionic-app-scripts serve"
    },
    "dependencies": {
        "@angular/common": "4.1.2",
        "@angular/compiler": "4.1.2",
        "@angular/compiler-cli": "4.1.2",
        "@angular/core": "4.1.2",
        "@angular/forms": "4.1.2",
        "@angular/http": "4.1.2",
        "@angular/platform-browser": "4.1.2",
        "@angular/platform-browser-dynamic": "4.1.2",
        "@ionic-native/core": "3.10.2",
        "@ionic-native/splash-screen": "3.10.2",
        "@ionic-native/status-bar": "3.10.2",
        "@ionic/storage": "^2.0.1",
        "cordova-android": "^6.2.3",
        "cordova-browser": "^4.1.0",
        "cordova-ios": "^4.4.0",
        "cordova-plugin-console": "^1.0.5",
        "cordova-plugin-device": "^1.1.4",
        "cordova-plugin-nativestorage": "^2.2.2",
        "cordova-plugin-splashscreen": "^4.0.3",
        "cordova-plugin-statusbar": "^2.2.2",
        "cordova-plugin-whitelist": "^1.3.1",
        "cordova-sqlite-storage": "^2.0.4",
        "ionic-angular": "3.3.0",
        "ionic-plugin-keyboard": "^2.2.1",
        "ionicons": "3.0.0",
        "rxjs": "5.1.1",
        "sw-toolbox": "3.6.0",
        "zone.js": "0.8.11"
    },
    "devDependencies": {
        "@ionic/app-scripts": "1.3.7",
        "@ionic/cli-plugin-cordova": "1.4.0",
        "@ionic/cli-plugin-ionic-angular": "1.3.1",
        "typescript": "2.3.3"
    },
    "description": "The balans app",
    "cordova": {
        "plugins": {
            "cordova-plugin-console": {},
            "cordova-plugin-device": {},
            "cordova-plugin-splashscreen": {},
            "cordova-plugin-statusbar": {},
            "cordova-plugin-whitelist": {},
            "ionic-plugin-keyboard": {},
            "cordova-sqlite-storage": {},
            "cordova-plugin-nativestorage": {}
        },
        "platforms": [
            "android",
            "browser",
            "ios"
        ]
    }
}

#8

How does your auth service look like?


#9
import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
import {Observable} from 'rxjs/Observable';
import {Http, RequestOptions, Headers} from "@angular/http";
import 'rxjs/add/operator/toPromise';
import 'rxjs/Rx';

@Injectable()
export class AuthService {

  static get parameters() {
    return [[Http]];
  }

  constructor(private http:Http, private storage: Storage) {
    alert('Auth service: Storage = ' + storage);
  }

  public login(loginData: any) {
    if (loginData.username === null || loginData.password === null) {
      return Observable.throw("Please insert credentials");
    } else {
      let encoded = btoa("balans:secret");
      let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Basic ' + encoded });
      let options = new RequestOptions({ headers: headers });

      let url = 'http://localhost:9001/oauth/token?grant_type=password&username=' + loginData.username + '&password=' + loginData.password;

      return this.http.post(url, '', options)
        .map(res => res.json())
        .catch(e => Observable.throw(e));
    }
  }

  public register(registerCredentials: any) {
    let headers = new Headers({ 'Content-Type': 'application/json' });
    let options = new RequestOptions({ headers: headers });

    return this.http.post('http://localhost:9001/api/auth/user/register', registerCredentials, options)
      .map(res => res.json())
      .catch(e => Observable.throw(e));
  }

  public getAccessToken() : boolean {
    alert( this.storage );

    this.storage.get('auth_token').then((access_token: string) => {
      console.log('Found access token.');

      if (access_token) {
        alert('Found');

        return access_token;
      } else {
        alert('Not Found');

        return null;
      }
    });

    return false;
  }

  public setAccessToken(accessToken: string) {
    console.log('Setting access token. Storage = ' + this.storage );

    this.storage.ready().then(() => {
      this.storage.set('access_token', accessToken);
    });
  }

  public removeAccessToken() {
    console.log('Removing access_token: ' );

    this.storage.remove('access_token');
  }

  public isLoggedIn() : boolean {
    console.log('Isloggedin: ' + (this.getAccessToken() !== null ) );

    return (this.getAccessToken() !== null );
  }

  public logout() {
    let headers = new Headers({ 'Content-Type': 'application/json', Authorization: 'Bearer ' + this.getAccessToken() });
    let options = new RequestOptions({ headers: headers });

    return this.http.post('http://localhost:9001/oauth/revoke-token', null, options)
      .map(res => res.json())
      .catch(e => Observable.throw(e));
  }
}

#10

Seems to be just fine to me. Did you test it out in another browser/on a real device?


#11

I just read a reaction to a youtube video that this only works on a real device and that it doesn’t work on browsers. Do you know if that is true?


#12

Oh wow did miss that. If you’re using cordova sqlite / nativestorage, yes this will only work on a real device. I thought you were using the plain storage without plugins. I don’t see any reason for you to use these native plugins, since ionic storage is excellent in deciding which storage provider to use for itself.

If you remove the plugins and just keep @ionic/storage (the plugins are optional), it will probably use indexedDB whenever it’s available and will fall back to localstorage etc.


#13

It’s set-up where it falls back to IndexedDB or LocalStorage when it’s on a browser. Installing the SQLite plugin just means it’ll use that when on an actual device.


#14

I have removed the “cordova-sqlite-storage”: “^2.0.4”, line. I deleted the package-lock.json file, deleted the node_modules directory. I then did a npm install and ionic serve. Unfortunately the error still occures, Storage is undefined.


#15

Wat happens in a blank project ?


#16

If you don’t get any better answers, I would nuke your entire node environment and reinstall it, using nvm or nvm-windows depending on your OS. Something odd is going on.


#17

I have more information. It turns out that when I do console.log in the constructor of app.component.ts then storage isn’t null. Then it is valid.

constructor( private storage: Storage, public events: Events, platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, private auth: AuthService, public menu: MenuController) {
  console.log( storage );
}

It is only undefined in Providers. Somehow ionic can’t inject the storage into providers. Is this is ionic bug or just how ionic works? If so then how can I access the storage in a provider?


#18

I found the answer. Finally. Should someone else get stuck with this problem, here is the solution. Add the following to your provider:

static get parameters() {
  return [[Http], [Storage]];
}

You must supply the parameter types apparently. Really reaaly wierd that

constructor(private http:Http, private storage: Storage)

isn’t good enough.


#19

Does it work if you remove parameters entirely? You shouldn’t need that as it was only used back when they were simultaneously supporting TS and JS.


#20

As @SigmundFroyd states, that’s really weird. Because that’s really oldschool, you shouldn’t need that get parameters at all.