Data provider getting called multiple times on startup


#1

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);

      });


#2

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.


#3

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.


#4

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


#5

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;
  }

}

#6

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.


#7

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;
  }

}

#8

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.


#9

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",


#10

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


#11

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.


#12

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.


#13

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


#14

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"
}


#15

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