Data provider getting called multiple times on startup

I have a data provider that I’m injecting into multiple tabs. It’s getData function is used to load data, it uses a flag to determine if it’s already loaded, but it is getting called multiple time before the loaded flag is set. So I thought I’d have a loading flag, and if set, wait for the other loading to finish, then resolve. But I do not know how to do that with a Promise.

I’m new to javascript and promises.

Thanks,
Jon

  getData(): Promise<DataModel> {

    return new Promise(resolve => {
      
      if (this.loaded) {
        console.log('data already loaded');
        resolve(this.myData);
      }

      if (this.loading) {
        console.log('loading data');

        //
        // wait for other load to finish, then resolve
        //
      }

      console.log('loading data');

      this.loading = true;

      this.storage.get('ptt-data').then((data) => {
        this.myData = DataModel.fromJSON(data);
        this.loaded = true;
        this.loading = false;

        resolve(this.myData);

      });

You are creating way too many unnecessary things. You don’t need to make a new Promise, you don’t want either loading or loaded flags, and you don’t want multiple code paths. All of those things only provide places for bugs to live. In fact, I would argue you might not even want getData() to be a function, since calling it multiple times won’t ever get you anything new.

I would refactor this to only expose data as a Promise<DataModel>:

data: Promise<DataModel>;
constructor(storage: Storage) {
  this.data = this.storage.ready()
    .then(() => this.storage.get('ptt-data'))
    .then((json) => DataModel.fromJSON(json));
}

Personally, I don’t like things like DataModel needing special methods like fromJSON(). I just make them interfaces, in which case that last line isn’t even needed. I assume you have some reason for doing unusual unmarshalling logic on these objects, and if that’s true, I would consider moving it out of DataModel and into the provider service, allowing DataModel to just be an interface.

I’m all for simpler. In trying your code, I’m getting the following:

Typescript Error
Argument of type 'LocalForage' is not assignable to parameter of type 'string'.
D:/development/ionic/ProjectTimeTracker2/src/providers/local-data.ts
    .then(() => this.storage.get('ptt-data'))
    .then((json) => DataModel.fromJSON(json));
}

I’m new to Javascript in general, so I may be doing things wrong. But the reason that I have the load from json and the opposite, is that I create some mappings of the objects created using string UUID keys. But in the JSON that I save I do not want the mappings.

I was assuming that your storage object was from ionic-storage. Is that true?

Yes.

import { Injectable } from '@angular/core';
import { DataModel } from '../models/data-model';
import { Storage } from '@ionic/storage';
import { Observable } from 'rxjs/observable';


@Injectable()
export class LocalData {
  data: any;
  myData: DataModel;

  tasksObservable: Observable<any>;
  tasksObserver: any;

  constructor(public storage: Storage) {
    this.myData = new DataModel();

    this.tasksObservable = Observable.create(observer => {
      this.tasksObserver = observer;
    });

    this.data = this.storage.ready()
      .then(() => this.storage.get('ptt-data'))
      .then((json) => DataModel.fromJSON(json));
  }

  getTasksUpdates(): Observable<any> {
    return this.tasksObservable;
  }

}

I guess you have to type json:

.then((json: string) => DataModel.fromJSON(json));

See if that works. Oh, and please stop using any. data is a Promise<DataModel>. I would also get rid of myData, as having the same thing in two places is asking for trouble.

I think I’m starting to understand. I replaced the myData usage in the tabs with the promise. So I’m making progress learning. Still get an error on that syntax:

Typescript Error
Argument of type '(json: string) => DataModel' is not assignable to parameter of type '(value: LocalForage) => DataModel | PromiseLike<DataModel>'. Types of parameters 'json' and 'value' are incompatible. Type 'LocalForage' is not assignable to type 'string'.
D:/development/ionic/ProjectTimeTracker2/src/providers/local-data.ts
      .then(() => this.storage.get('ptt-data'))
      .then((json: string) => DataModel.fromJSON(json));

Code looks like:

import { Injectable } from '@angular/core';
import { DataModel } from '../models/data-model';
import { Storage } from '@ionic/storage';

@Injectable()
export class LocalData {
  myDataPromise: Promise<DataModel>;


  constructor(public storage: Storage) {

    this.myDataPromise = this.storage.ready()
      .then(() => this.storage.get('ptt-data'))
      .then((json: string) => DataModel.fromJSON(json));

  }

  getData(): Promise<DataModel> {
    return this.myDataPromise;
  }

}

I’m curious what version of @ionic/storage you have, because I have virtually identical code in a scratch project that works:

export class CarInfo {
  plate: string;
  brand: string;
  model: string;

  static fromJSON(json: string): CarInfo {
    return JSON.parse(json);
  }
}

@Injectable()
export class CarInfoDetection {
  car: Promise<CarInfo>;
  constructor(storage: Storage) {
    let seed = new CarInfo();
    seed.plate = 'abc123';
    seed.brand = 'audi';
    seed.model = '5000';
    this.car = storage.ready()
      .then(() => storage.set('car', JSON.stringify(seed)))
      .then(() => storage.get('car'))
      .then((json: string) => CarInfo.fromJSON(json));
  }

  getInfo(picture): Promise<CarInfo> {
    return this.car;
  }
}

export class HomePage {
  car = {} as CarInfo;
  constructor(cars: CarInfoDetection) {
    cars.car.then((car) => this.car = car);
  }
}

<div>plate: {{car.plate}}</div>

Displays “plate: abc123” as expected, no build errors.

Argh, I probably screwed something up. But no idea what. I think this shows the versions I have???

package.json

    "@ionic/storage": "2.0.1",
...
    "cordova-sqlite-storage": "^2.0.4",

I have 2.0.1 as well. Can you spot any significant difference between your DataModel and my CarInfo?

I do not see any major differences. I’m going to try a scratch app, then copy the provider and the datamodel and see if it compiles.

Definitely something in my environment. I did a ionic start ProjectTimeTracker blank. Then created a file for the 1st two classes. Saving the 2nd file got the same error on your code. Maybe I’ll reinstall ionic and see if that helps.

One other possibility is TypeScript version. My scratch project has 2.3.2.

My typescript seems older, I’ll see if I can update it.

{
  "name": "ProjectTimeTracker",
  "version": "0.0.1",
  "author": "Ionic Framework",
  "homepage": "http://ionicframework.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.0",
    "@angular/compiler": "4.1.0",
    "@angular/compiler-cli": "4.1.0",
    "@angular/core": "4.1.0",
    "@angular/forms": "4.1.0",
    "@angular/http": "4.1.0",
    "@angular/platform-browser": "4.1.0",
    "@angular/platform-browser-dynamic": "4.1.0",
    "@ionic-native/core": "3.7.0",
    "@ionic-native/splash-screen": "3.7.0",
    "@ionic-native/status-bar": "3.7.0",
    "@ionic/storage": "2.0.1",
    "ionic-angular": "3.2.1",
    "ionicons": "3.0.0",
    "rxjs": "5.1.1",
    "sw-toolbox": "3.6.0",
    "zone.js": "0.8.10"
  },
  "devDependencies": {
    "@ionic/app-scripts": "1.3.7",
    "@ionic/cli-plugin-ionic-angular": "1.1.2",
    "typescript": "2.2.1"
  },
  "description": "An Ionic project"
}

You sir are a genius, updated the TypeScript version and the error went away. Thanks much.