Synchronising Setting/Getting Objects and values from Ionic Storage

Hi guys, I’m doing a simple weather app project for college in Ionic v3 and I’m having a real headache using ionic storage to return values back that have been passed in to storage. The application is fairly basic, GET lat/lon values from an API, save them in storage and then pass them to another API to GET more information and display it on a home page.

I can get the values from the API initially straightforward enough and I’ve pulled the relevant information I need from the API, stored them in objects, cityInfo, weatherInfo, countryNews inside providers(below, this may be horribly incorrect way to do it so all recommendations are welcome.)

private cityInfo = {
    cca2: "",
    commonName: "",
    flag: "",
    lat:  0,
    lng: 0
  };

then I’ve set the value in storage as follows

packCityInfo(cityData: any): void {
    this.cityInfo.cca2 = (cityData[0].cca2);
    this.cityInfo.commonName = (cityData[0].name.common);
    this.cityInfo.flag = (cityData[0].flags.png);
    this.cityInfo.lat = (cityData[0].latlng[0]);
    this.cityInfo.lng = (cityData[0].latlng[1]);
this.storage.set('cityInfo', this.cityInfo);

If I console.log(cityInfo) it prints the different values for the different properties correctly i.e.

cca2: "GB"
commonName: "United Kingdom"
flag: "https://flagcdn.com/w320/gb.png"
lat: 54
lng: -2

The problem I’m having is figuring out how to then access these values out of storage. I think the issue I’m having is around synchronicity. I found that the first time I try to save my settings the request will fail and say that the “.lat is undefined” but the second time I save my settings the request will succeed and I’ll get information from the API request from storage.

My saveSettings() function In my settings.ts page the following

this.storage.set('city', this.cityName);
    this.storage.set('units', this.units);

    this.cityDataService.getCityDataFromAPI(this.cityName);

    this.storage.get("cityInfo").then((val) => {
      let cityInfo = val;
      // return val;
      this.weatherService.getWeatherFromApiCoordinates(cityInfo.lat, cityInfo.lng, this.units);
    }).catch((err) => {
      console.log(err);
    });

Returns “TypeError: Cannot read properties of null (reading ‘lat’) at settings.ts:48”

The Services are pretty standard and just handle the http get requests so I don’t think the issue is with them? (Could be wrong ofc)

getWeatherFromApiCoordinates(lat: number, lon: number, units: string) : void  {
    let weatherData = this.http.get('https://api.weatherbit.io/v2.0/current?lat=' + lat + '&lon=' + lon + "&key=" + this.apiKey + '&units=' + units);
    weatherData.subscribe(data => {
      let currentWeather = data;

      this.packWeatherData(currentWeather);
    });
  }

The second time the saveSettings() function is run it will correctly display values but its pulling from the previously set values. So for example I run

  1. Dublin,
  2. Dublin,
  3. France. It will return 1.Err, 2.Dublin, 3.Dublin.

Each time storage is cleared it will run Error initially obv

I’ve tried marking the GET functions as async and awaiting the return , similar with the set and get storage but its not worked just yet.

The full project is here On github
Having a bit of a nightmare closing this one out (also we’re limited to specifically using
ionic 5.4.4
Angular CLI: 8.3.8
Node: 10.16.3

Package Version

@angular-devkit/architect 0.803.8
@angular-devkit/core 8.3.8
@angular-devkit/schematics 8.3.8
@schematics/angular 8.3.8
@schematics/update 0.803.8
rxjs 6.4.0

Any and all advice will be appreciated < 3

Hi
you seem to be making a few fundamental mistakes while working in angular

The providers you defined should have a function that gives the data as obtained from the API. Their role is to be the single source of truth with an api that allows for typechecking and obscuring all dirty wiring underneath

You are using storage to do inter-class/component communication - which by design is wrong, error prone, giving you the async issues you are referring to (as the GETter has no way telling the SETter has finishd) and there is no type-safety. In in plain simple words: don’t do this.

Storage is to be used within the provider to get and set upon the lifecycle of the provider (=app)

So far the analysis, now it’s your turn to try to get to the code!

In addition to @Tommertom’s typically excellent advice, if I were your professor, I would ding you a full letter grade for each voluntary any in your code. Always take the time to draw up proper interfaces for business objects like CityInfo - they make your code both orders of magnitude more readable and help your development environment help you avoid stupid typo bugs.

I have not kept detailed track of Ionic’s internal version dependencies, but the restrictions you noted in your first post smell off to me. Perhaps somebody with the Ionic team could comment further, but Angular 8 and Node 10 both strike me as dangerously low to go with Ionic 5.

Hey thanks for the advice. So to clarify, I need to rework my providers. Currently they’re stripping out the important information from the API, putting it into an obj then passing it to storage. Should the reformatting of the API call be handled separately?

What I do in these situations is to define two parallel tracks of business interfaces: one describing what is going to come from the backend and one representing the easiest format for the view layer to display. You can use any naming convention you wish as long as it’s consistent: I tend to use Foo and WiredFoo, so we would have:

interface Foo {
  // this is what components see
}

interface WiredFoo {
  // this is what comes over the wire
}

class FooProvider {
  constructor(private http: HttpClient) {}

  fooByName(name: string): Observable<Foo> {
    return this.http.get<WiredFoo>(...).pipe(
      map(wired => this.unwireFoo(wired)));
  }

  private unwireFoo(wired: WiredFoo): Foo {
    // work your magic
  }
}

Unless your assignment specifically demands that the app have an offline mode, I would not get device storage involved at all…

Hey thanks for the reply. Yeah I should have put a disclaimer that its all very messy as I’ve been taking stuff out and throwing stuff around. Its very far from final and I know theres lots of visibility and type issues/bad habits which I’m not too worried about. I have plenty of OOP Java experience and It’ll be tidied long before submission. I understand the concerns about version dependencies we’ve had a back and forth with our lecturers on the requirements and there have been a lot of headaches in the class around keeping very specific (for seemingly no reason) versions.

RE: Storage I’m afraid its part of the requirements.

I’m out, then. Artificial requirements irritate me.

1 Like

In my view, the provider should:

  • get the data from the remote API, cleanse it, and store in private variable in the class
  • store the data in storage, in case the requirement is to have some sort of persistence
  • get the data from storage in case the API call cannot be made (persistence)
  • when another object/page/component needs the data, the provider gives the data from the private variable through a method (giveMyData():TypeOfData { } )
  • if needed, upon request from a page/component, change the data or do something else with it and return derived data

And ideally each method in the provider starts with the word return (or at least ends with it)

If there is no persistence requirement, then storage isn’t a smart thing to include.

Hey, Thanks that’s a really clear and easy to follow critique. I’ll go about implementing it.
Persistence is a requirement of the project brief so it has to be included.

Correction: this also will expose the data directly and if u r tempted to manipulate the data outside the provider then the principles are broken: the provider wouldn’t know and cannot do its internal checks and balances (e.g. updating for persistence)

Different ways to fix this: best is to use Observables, also given the async nature of web dev. Weaker alternatives: be very disciplined or maybe even clone the content in the function, so you enforce segregation of responsibilities (or however that principle is called)

I wasn’t thinking of this because imho that is lesson #2 in this program :grinning:

Here I hope @rapropos will chip in - he posted some great posts on this topic

2 Likes