Welcome to the 5. lesson of the Ionic Crash Course!
Today we will tackle another very common obstacle when building a mobile app: Where to store the data of your app?
Lucky us, there are already great solutions out there which embrace either the localstorage of a browser or even the native device memory. We will start with another great package from Ionic.
1. Setting up Ionic Storage
Ionic Storage is our go to package for easily managing data. With Ionic Storage we can save JSON objects and key/value pairs to different storage engines, unified through one interface.
Ok in easy this means, Storage will internally select which storage engine is available and select the best possible solution for us. If you run the preview, it will try IndexedDB, WebSQL and finally localstorage.
The problem with localstorage in general is that this can get cleaned from the OS of a mobile device and you loose all data. Not a very good idea.
To get the best soring solution on a device we now need to add a new Cordova plugin which will allow access to the SQLite storage which Ionic Storage will then use internally. Go ahead and run:
1 |
ionic cordova plugin add cordova-sqlite-storage --save |
The Storage package is already bundled with our app, we don’t have to install something new. We only want to generate a new provider which will handle our connection to the storage, so also run:
1 |
ionic g provider favorite |
Of course we now need to change a few things inside our src/app/app.module.ts as you might already expect. We need to hook up our new provider and also add the IonicStorageModule to our module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyApp } from './app.component'; import { HttpClientModule } from '@angular/common/http'; import { ApiProvider } from './../providers/api/api'; import { EmailComposer } from '@ionic-native/email-composer'; import { FavoriteProvider } from './../providers/favorite/favorite'; import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [ MyApp ], imports: [ BrowserModule, HttpClientModule, IonicModule.forRoot(MyApp), IonicStorageModule.forRoot() ], bootstrap: [IonicApp], entryComponents: [ MyApp ], providers: [ StatusBar, SplashScreen, ApiProvider, FavoriteProvider, EmailComposer, {provide: ErrorHandler, useClass: IonicErrorHandler} ] }) export class AppModule {} |
That’s it, we are now ready to use the Storage!
2. Working with Ionic Storage
Like in the HTTP lesson before it’s a good idea to maintain your logic in a provider and not directly inside the class of the view. We have created a FavoriteProvider
, because we want to be able to start films which we like the most.
So we need to be able to store the IDs of those films, check if a current film is already a favorite and of course revert everything again.
Not yet a member?
Join the Ionic Academy now and enjoy the full course library and learn everything Ionic!
With Ionic Storage we can either call set()
or get()
and both will return a Promise. Unlike our Observable from before this is only handled with a then() block, but the operation is still async!
If we want to get a list of all IDs we can easily return the Storage value for our predefined STORAGE_KEY
.
To favorite a film, we need to retrieve the list of all films already inside the storage and then either add the film or set a completely new object if nothing is there yet.
Don’t worry about duplicates, we will handle this from the page later.
To remove a film we use more or less the same logic, but now we get the index of our film inside the array and call the splice method to remove it from the array before we save it back.
Open your src/providers/favorite/favorite.ts and replace it with:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage'; const STORAGE_KEY = 'favoriteFilms'; @Injectable() export class FavoriteProvider { constructor(public storage: Storage) { } isFavorite(filmId) { return this.getAllFavoriteFilms().then(result => { return result && result.indexOf(filmId) !== -1; }); } favoriteFilm(filmId) { return this.getAllFavoriteFilms().then(result => { if (result) { result.push(filmId); return this.storage.set(STORAGE_KEY, result); } else { return this.storage.set(STORAGE_KEY, [filmId]); } }); } unfavoriteFilm(filmId) { return this.getAllFavoriteFilms().then(result => { if (result) { var index = result.indexOf(filmId); result.splice(index, 1); return this.storage.set(STORAGE_KEY, result); } }); } getAllFavoriteFilms() { return this.storage.get(STORAGE_KEY); } } |
If we just want to check whether a film is already stored we retrieve the array and check if the key can be found inside.
Now we have already implemented all of our business logic for storing, it’s time to change our UI so we can actually use those functions!
3. Using our Favorite Provider
Whenever we enter our FilmDetailsPage
we want to check whether the current film is already a favorite or not.
Because inside our view we will use a new syntax called *ngIf
which allows us to test if a condition is true or not. By doing this we can add the code for 2 different buttons, but only one will be displayed at a time!
The condition will check for a variable inside our class, so start with the view and change your src/pages/film-details/film-details.html to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<ion-header> <ion-navbar color="primary"> <ion-title>{{ film.title }}</ion-title> <ion-buttons end> <button ion-button icon-only (click)="unfavoriteFilm()" *ngIf="isFavorite"><ion-icon name="star"></ion-icon></button> <button ion-button icon-only (click)="favoriteFilm()" *ngIf="!isFavorite"><ion-icon name="star-outline"></ion-icon></button> </ion-buttons> </ion-navbar> </ion-header> <ion-content padding> <ion-card> <ion-card-content> {{ film.opening_crawl }} </ion-card-content> </ion-card> <button ion-button full (click)="shareFilm()">Share by Email</button> </ion-content> |
So we got either an outline button or a full button, depending on the state of isFavorite
. Inside the class we now have to take care of setting the value correctly.
But as we have implemented our provider so super clear and clean before, it’s absolutely no problem to build the page, right?
After we get the data for the current film we pass the ID to our provider and first of all check if that film is already one of our favorites. Inside the then()
block we can use the result and set it to the variable for our condition.
To favorite or remove a favorite we can rely on the functions we implemented previously inside the provider, and inside the callback block we just change our local variable manually. We could also use a return value here but we want to keep things simple, and it should do the job for us!
So now go ahead and change your src/pages/film-details/film-details.ts to:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
import { FavoriteProvider } from './../../providers/favorite/favorite'; import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { EmailComposer } from '@ionic-native/email-composer'; @IonicPage() @Component({ selector: 'page-film-details', templateUrl: 'film-details.html', }) export class FilmDetailsPage { film: any; isFavorite = false; constructor(public navCtrl: NavController, public favoriteProvider: FavoriteProvider, public navParams: NavParams, private emailComposer: EmailComposer) { this.film = this.navParams.get('film'); this.favoriteProvider.isFavorite(this.film.episode_id).then(isFav => { this.isFavorite = isFav; }) } favoriteFilm() { this.favoriteProvider.favoriteFilm(this.film.episode_id).then(() => { this.isFavorite = true; }); } unfavoriteFilm() { this.favoriteProvider.unfavoriteFilm(this.film.episode_id).then(() => { this.isFavorite = false; }); } shareFilm() { let email = { to: 'saimon@devdactic.com', subject: 'I love this one: ' + this.film.title, body: 'Can you remember the opening?<br><br>\"' + this.film.opening_crawl + '\"', isHtml: true }; this.emailComposer.open(email); } } |
You can now navigate to the details pages and should be able to star and unstar a film!
Of course this is not only a tiny effect, it’s actually stored inside the database. You can navigate to other movies, star them and you will see that your selecting will be stored all the time, even if your refresh the window!
4. Next Steps
Congratulations on finishing the 5. lesson!
You are now aware of how to store and save important data inside your app.
In the next lesson we will take a look at how to easily style our app and change the appearance within minutes. Really.
Happy Coding,
Simon