Ionic 3 - Créez votre application mobile

Lamine BENDIB

Vous maîtrisez les technos du web et souhaitez développer une application mobile ? Ne cherchez plus ! Ionic est fait pour vous.

Ionic est un Framework permettant la création d'applications cross-platform rapidement et facilement en utilisant des technologies web (JavaScript, HTML, CSS). Il s'appuie sur Angular pour la partie web et sur Cordova pour la partie native.

Dans ce tutoriel, nous allons apprendre les bases de l'utilisation d'Ionic. Nous allons créer une application from scratch qui nous permettra de faire un tour des fonctionnalités de ce Framework. Vous trouverez sur Github tout le code présenté dans ce tutoriel.

- Titre : Ionic 3 - Créez votre application mobile

- Auteur : Lamine BENDIB

- Parution : 19 mars 2018

- Licence : Copyright ® 2018 Soat. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Installation et mise en route du projet

- I-A. Prérequis

- I-A-1. Installation

- I-A-2. Création du projet

- II. Anatomie d'un projet Ionic

- II-A. /

- II-A-1. src/

- II-A-2. www/

- II-A-3. resources/

- III. Création des pages

- IV. Navigation Stack

- IV-A. Principe

- IV-B. En pratique

- V. Lifecycle events

- VI. Application V1.0

- VI-A. Création de la root page

- VI-B. Navigation dans l'application

- VI-B-1. MyMoviesPage → MovieListPage

- VI-B-2. MovieListPage → MovieDetail

- VI-C. Création de notre premier provider

- VI-D. Gestion des favoris

- VII. Ionic storage

- VII-A. Installation

- VII-B. Provider pour les opérations de CRUD

- VII-C. Application

- VIII. Conclusion

I - Installation et mise en route du projet

I-A - Prérequis

- Notions en TypeScript/JavaScript

- Notions d'Angular (v2+)

- Notions sur npm, installé avec Node.js

I-A-1 - Installation

Tout d'abord, il nous faut installer Ionic avec Cordova et ce en utilisant npm => npm install -g cordova ionic

I-A-2 - Création du projet

Ionic nous met à disposition une CLI (Commande Line Interface) pour, entre autres, la création d'un projet à partir d'un template contenant toutes les dépendances nécessaires pour démarrer => ionic start ionic3-movie-app sidemenu

Cette commande nous permet de créer le projet “ionic3-movie-app” en se basant sur le template sidemenu.

Pour lancer l'application, il suffit de se mettre dans le dossier et de lancer la commande ionic serve. Chrome DevTools fournit un outil nous permettant de simuler la taille des écrans sur mobile, ce qui est plus pratique pour tester notre application :

Il est aussi possible de déployer l'application sur un mobile / émulateur Android mais pour cela il faut installer le SDK et configurer le path. La démarche étant assez fastidieuse et au-delà du scope de ce tutoriel, vous trouverez toutes les informations nécessaires ici.

Une fois le SDK installé et votre téléphone branché en USB, voici les démarches à suivre pour lancer l'application sur ce dernier :

- Ajouter Android comme plateforme : ionic plateform add android

- Lancer le build : ionic build android

- Lancer l'application : ionic cordova run android (la fonction débogage doit être activée)

Vous pouvez aussi créer des appareils virtuels grâce à l'AVD Manager et émuler l'application avec la commande ionic cordova emulate android.

Au moment où j'écris ces lignes, un nouvel outil est sorti, Ionic DevApp, qui permet de lancer et de déboguer l'application sans avoir à installer des SDK complémentaires.

Pour ce tutoriel, un navigateur sera amplement suffisant.

II - Anatomie d'un projet Ionic

Ionic se base sur les components, ce qui veut dire que chaque page est vue comme un composant individuel avec son propre habillage et comportement. Cela facilite la maintenabilité du projet vu que tout le code se trouve au niveau du composant.

Voici la structure de notre projet :

II-A - /

À la racine de notre projet, nous avons des fichiers de configuration dont les plus importants sont :

- config.xml : utilisé par Cordova ; contient la configuration essentielle au build de l'application pour les plateformes iOS et Android.

- package.json : il s'agit du fichier de config de npm, contenant des informations sur le projet (nom, version, auteur, licence…) et ses dépendances en terme de packages npm.

II-A-1 - src/

Le code du projet vit ici. C'est ici que nous allons créer des pages et des services. Ce dossier contient plusieurs sous-dossiers :

- src/app : contient le composant racine (root component), point d'entrée de notre application où l'on définit notre root page, déclare nos modules, etc.

- src/assets: contient tous les assets statiques, que ce soient des images ou des données en JSON.

- src/pages : contient toutes les pages de l'application. Ionic CLI génèrera automatiquement les pages dans ce dossier

- src/provider : contient les services (appelés ici provider). Ionic CLI génèrera automatiquement les providers dans ce dossier.

- src/theme : contient les variables SCSS pour les couleurs de l'application, etc.

II-A-2 - www/

C'est le dossier web qui contient le code compilé de notre application.

II-A-3 - resources/

Contient les ressources pour les plateformes Android et iOS, notamment les images utilisées pour le splash screen et les icônes.

Maintenant que tout est installé et que la structure de projet d'Ionic n'a plus aucun secret pour nous, nous pouvons commencer à créer nos premières pages.

III - Création des pages

Notre application contiendra notre home page qui affichera nos films favoris, une page pour afficher la liste des films et une autre page pour le détail du film (image, description, note, trailer…). On peut choisir nos films préférés à partir de la liste et stocker le tout dans une base de données locale. On aura le résultat final suivant :

Ionic CLI nous permet de générer nos pages avec la commande ionic g page page-name.

Si vous avez déjà utilisé Angular CLI, vous remarquerez que la commande est assez similaire. Pour plus d'informations sur les possibilités de génération de fichiers, je vous invite à lire la doc d'Ionic CLI.
Nous allons donc générer nos fichiers pour chaque page :

ionic g page my-movies
ionic g page movie-list
ionic g page movie-detail

Pour chaque page, nous aurons quatre fichiers : Le template (.html), la feuille de style (.scss), le module (module.ts), et la page (.ts). Après cela il faut déclarer les modules dans le tableau des imports de app.module.ts

1. @NgModule({

2. declarations: [MyApp],

3. imports: [BrowserModule, IonicModule.forRoot(MyApp),  MyMoviesPageModule, MovieListPageModule, MovieDetailPageModule],

4. bootstrap: [IonicApp],

5. entryComponents: [MyApp],

6. providers: [

7. StatusBar,

8. SplashScreen,

9. { provide: ErrorHandler, useClass: IonicErrorHandler }

10. ]

11. })

Maintenant que nous avons créé nos pages, il faut leur mettre du contenu et naviguer vers ces pages. Mais avant cela il faut que je vous parle du système de navigation d'Ionic.

IV - Navigation Stack

IV-A - Principe

Pour naviguer entre les vues dans Angular 4, on utilise un système de routage. Dans Ionic, on utilise plutôt une pile de navigation (navigation stack), ce qui implique de mettre des vues dans la pile (push) et de les enlever (pop) comme décrit sur le schéma suivant :

C'est toujours la page se trouvant en haut de la pile qui sera active :

Ici on est sur la root page et l'on souhaite naviguer vers la Page 1 : on la push dans la stack et vu qu'elle se trouve maintenant en haut de la pile, elle sera donc affichée à l'utilisateur. Si l'on souhaite revenir en arrière, on pop la page de la stack et c'est la root page qui sera affichée.

Les vues sont créées lorsqu'elles sont ajoutées à la stack de navigation. Par défaut, tant qu'elles y sont, elles sont mises en cache et laissées dans le DOM. Elles seront détruites dès qu'elles sont supprimées de la stack.

IV-B - En pratique

La navigation se fait avec le NavController dont les trois méthodes principales sont :

- push() : permet d'insérer la vue dans la stack. Si la page contient un ion-navbar, un bouton “Retour” sera automatiquement ajouté. On peut aussi passer des paramètres qui seront récupérés grâce au module NavParams.

- pop() : permet de supprimer la vue de la stack et de naviguer vers la vue précédente.

- popToRoot() : permet de supprimer toutes les vues de la stack et de naviguer vers la root page.

Il est souvent nécessaire d'exécuter des tâches au chargement de la page (rafraîchissement du contenu, etc.) ou lorsque l'on quitte la page active (désabonnement aux observables, stockage de données, etc.). C'est là qu'entrent en jeu les lifecycle events.

V - Lifecycle events

Les lifecycle events, comme leur nom l'indique, sont des événements nous informant de l'état d'une page Ionic durant tout son cycle de vie, de sa création à sa destruction. Certains ne sont lancés qu'une seule fois tandis que d'autres sont lancés à chaque fois que la page concernée est active.

- ionViewDidLoad : se lance pour signaler que toutes les variables et dépendances sont prêtes à l'emploi, ce qui implique aussi que la page a été ajoutée en mémoire et qu'elle sera en cache. Cet événement n'est lancé qu'une seule fois. C'est ici que l'on met le code d'initialisation et de configuration de la page.

- ionViewWillEnter : même si la page est complètement chargée, ce n'est pas encore la page active et elle n'est pas encore visible pour l'utilisateur, et Ionic exécute encore des tâches en background. Néanmoins, on peut manipuler les éléments de la page avant qu'elle ne soit affichée. À utiliser pour les actions que l'on souhaite réaliser à chaque fois que l'on affiche la page (mise à jour d'une table…).

- ionViewDidEnter : cette fonction nous signale que tout s'est bien passé et que notre vue a été correctement affichée. Mais à quoi sert-elle et quand l'utiliser ? Eh bien, c'est l'endroit où l'on déclenche une fonctionnalité de l'application que nous souhaitons montrer à l'utilisateur à l'affichage de la page (animations, changelog de l'appli….).

- ionViewWillLeave : se lance lorsque l'on est sur le point de quitter la page. Elle est toujours considérée comme étant la page active mais elle est sur le point d'être supprimée.

- ionViewDidLeave : cet événement nous signale que la page n'est plus la page active. La fonction se lance après le ionViewDidEnter() de la page suivante. Mais que faire de cette fonction ? C'est un très bon endroit pour sauvegarder des données ou pour lancer des opérations en background qui n'ont pas besoin que la vue soit visible.

- ionViewWillUnload : c'est la dernière fonction à se lancer dans le cycle de vie d'une page. Elle ne se lance qu'une seule fois, et seulement quand on navigue en arrière. Par exemple si l'on navigue de la Page 1 à la Page 2, l'événement ne se lancera pas pour la Page 1 car elle est dans le cache (souvenez-vous de la stack de navigation), par contre si l'on fait un retour en arrière de la Page 2 vers la Page 1, cet événement se lancera pour la Page 2, car cette page n'est plus en mémoire ; elle sera donc déchargée (unloaded).

Pour plus d'informations, je vous conseille de lire la doc sur le NavController à la rubrique “Lifecycle events”.

Appliquons ensemble les concepts que nous venons d'apprendre à notre projet.

VI - Application V1.0

VI-A - Création de la root page

Première chose à faire : déclarer notre root page (page de démarrage) dans app.component.ts. Dans notre cas, il s'agit de MyMoviesPage :

1. import { MyMoviesPage } from "../pages/my-movies/my-movies";

2. import { Component, ViewChild } from "@angular/core";

3. import { Nav, Platform } from "ionic-angular";

4. import { StatusBar } from "@ionic-native/status-bar";

5. import { SplashScreen } from "@ionic-native/splash-screen";

7. @Component({

8. templateUrl: "app.html"

9. })

10. export class MyApp {

11. @ViewChild(Nav) nav: Nav;

13. rootPage  = MyMoviesPage;

14. }

Ensuite, mettons du contenu dans my-movies.html :

1. <ion-header>

2. <ion-navbar color="primary">

3. <button menuToggle ion-button icon-only>

4. <ion-icon name="menu"></ion-icon>

5. </button>

6. <ion-title>Movie App</ion-title>

7. </ion-navbar>

9. <ion-toolbar color="secondary">

10. <ion-title>My favorite movies</ion-title>

11. </ion-toolbar>

12. </ion-header>

14. <ion-content>

15. <ion-card>

16. <ion-card-content>

17. <p>You haven't selected any movie</p>

18. <p>Please select one by using the button below</p>

19. <button ion-button icon-left full (click)="findMovie()">

20. <ion-icon name="search"></ion-icon>

21. On going movies

22. </button>

23. </ion-card-content>

24. </ion-card>

25. </ion-content>

J'ai espacé le code HTML pour bien voir les différentes parties de la page :

- Header contient un ion-navbar qui est notre barre de navigation avec un hamburger button pour afficher le menu latéral. Il a comme titre le nom (très original) de notre application “Movie App”, mais vu que notre home page contiendra nos films favoris, j'ai donc ajouté, en dessous, un ion-toolbar avec le nom de la page dessus. Vous avez sûrement remarqué les attributs color avec les valeurs “primary” et “secondary” ; ces valeurs sont configurables dans theme/variables.scss.

- Content est notre body : c'est ici que nous mettrons notre contenu. J'ai choisi d'utiliser ion-card mais vous pouvez utiliser ce que vous souhaitez. Le bouton contient une icône avec du texte, et j'utilise de l'event binding pour faire appel à ma fonction.

Pour plus d'informations sur les composants Ionic, je vous invite à lire la doc qui est assez bien faite et possède pas mal d'exemples.

VI-B - Navigation dans l'application

Voici à quoi ressemblera la stack de navigation de l'application :

MyMoviesPage sera la page de démarrage. De là on peut soit accéder au détail d'un film favori en naviguant vers MovieDetailPage, soit afficher la liste des films dans MovieListPage et ensuite voir le détail d'un film.

Nous allons développer, un à un, les cas de navigation.

VI-B-1 - MyMoviesPage → MovieListPage

Dans notre template, lors du clic sur le bouton, nous faisons appel à la méthode findMovie(). Implémentons cette méthode dans MyMoviesPage :

1. export class MyMoviesPage {

2. constructor(public navCtrl: NavController, public navParams: NavParams) {}

4. ionViewDidLoad() {

5. console.log("ionViewDidLoad MyMoviesPage");

6. }

8. findMovie() {

9. this.navCtrl.push(MovieListPage);

10. }

11. }

Avec le NavController on push MovieListPage en haut de la pile. On est donc redirigé vers cette page, et un bouton de retour y est automatiquement ajouté :

Maintenant, il nous faut ajouter du contenu dans la page Movie List :

1. <ion-header>

2. <ion-navbar color="primary">

3. <button ion-button menuToggle>

4. <ion-icon name="menu"></ion-icon>

5. </button>

6. <ion-title>Movie List</ion-title>

7. </ion-navbar>

8. </ion-header>

11. <ion-content padding>

12. <ion-grid *ngIf="movies">

13. <ion-row>

14. <ion-col col-4 *ngFor="let movie of movies">

15. <img [src]="movie.poster_path" (click)="goToDetail(movie)" />

16. </ion-col>

17. </ion-row>

18. </ion-grid>

19. </ion-content>

Créons la variable movies dans movie-liste.ts et mettons-y des valeurs en les copiant depuis le fichier movies.json.

1. import { Component } from "@angular/core";

2. import { IonicPage, NavController, NavParams } from "ionic-angular";

4. @IonicPage()

5. @Component({

6. selector: "page-movie-list",

7. templateUrl: "movie-list.html"

8. })

9. export class MovieListPage {

10. movies: IMovie[] = [

11. {

12. vote_count: 666,

13. id: 19404,

14. video: false,

15. vote_average: 9.1,

16. title: "Dilwale Dulhania Le Jayenge",

17. popularity: 50.154262,

18. poster_path:

19. "https://image.tmdb.org/t/p/w185/2gvbZMtV1Zsl7FedJa5ysbpBx2G.jpg",

20. original_language: "hi",

21. original_title: "Dilwale Dulhania Le Jayenge",

22. genre_ids: [35, 18, 10749],

23. backdrop_path: "/nl79FQ8xWZkhL3rDr1v2RFFR6J0.jpg",

24. adult: false,

25. overview:

26. "Chaudhry Baldev Singh est un père de famille installé à Londres. Un jour, il reçoit une lettre d'Inde : son meilleur ami lui écrit, lui rappellant la promesse qu'il avait faite deux décennies auparavant de marier leurs enfants. Chaudhry décide alors de tenir sa promesse, mais donne toutefois un mois libre à sa fille tout avant qu'elle ne s'en aille en Inde se marier...",

27. release_date: "1995-10-20"

28. },

29. {

30. vote_count: 8482,

31. id: 278,

32. video: false,

33. vote_average: 8.5,

34. title: "Les Évadés",

35. popularity: 76.107673,

36. poster_path:

37. "https://image.tmdb.org/t/p/w185/5cIUvCJQ2aNPXRCmXiOIuJJxIki.jpg",

38. original_language: "en",

39. original_title: "The Shawshank Redemption",

40. genre_ids: [18, 80],

41. backdrop_path: "/xBKGJQsAIeweesB79KC89FpBrVr.jpg",

42. adult: false,

43. overview:

44. "En 1947, Andy Dufresne, un jeune banquier, est condamné à la prison à vie pour le meurtre de sa femme et de son amant. Ayant beau clamer son innocence, il est emprisonné à Shawshank, le pénitencier le plus sévère de l'Etat du Maine. Il y fait la rencontre de Red, un Noir désabusé, détenu depuis vingt ans. Commence alors une grande histoire d'amitié entre les deux hommes...",

45. release_date: "1994-09-23"

46. },

47. {

48. vote_count: 1099,

49. id: 372058,

50. video: false,

51. vote_average: 8.5,

52. title: "Your Name",

53. popularity: 57.569033,

54. poster_path:

55. "https://image.tmdb.org/t/p/w185/xq1Ugd62d23K2knRUx6xxuALTZB.jpg",

56. original_language: "ja",

57. original_title: "君の名は。",

58. genre_ids: [10749, 16, 18],

59. backdrop_path: "/7OMAfDJikBxItZBIug0NJig5DHD.jpg",

60. adult: false,

61. overview:

62. "Mitsuha est une lycéenne, la fille du maire d'une petite ville nichée entre les montagnes. Vivant avec sa petite sœur et sa grand-mère, c'est une demoiselle franche qui n'hésite pas à dire qu'elle n'a pas envie de participer aux rituels shinto, ou d'aider son père dans ses campagnes électorales. En fait, elle rêve de pouvoir quitter cette ville où elle s'ennuie, pour partir tenter sa chance à la capitale...",

63. release_date: "2016-08-26"

64. }

65. ];

67. constructor(public navCtrl: NavController, public navParams: NavParams) {}

69. ionViewDidLoad() {

70. console.log("ionViewDidLoad MovieListPage");

71. }

73. goToDetail(movie: IMovie) {

74. }

75. }

Personnellement, j'aime bien typer mes variables. Du coup, j'ai créé une interface IMovie en me basant sur la réponse JSON, en utilisant l'outil en ligne json2ts qui simplifie la tâche.

Et voilà :

Vous avez dû remarquer la fonction goToDetail(movie: IMovie) (je sais que je ne peux rien vous cacher ^^). Nous allons donc l'implémenter et voir comment récupérer des données de la stack de navigation.

VI-B-2 - MovieListPage → MovieDetail

Nous pouvons passer des paramètres supplémentaires au NavController en plus de la page. Dans notre cas, nous allons lui passer notre objet movie :

1. export class MovieListPage {

2. constructor(public navCtrl: NavController, public navParams: NavParams) { }

4. goToDetail(movie: IMovie) {

5. this.navCtrl.push(MovieDetailPage, movie);

6. }

7. }

Mettons un peu d'html dans movie-detail.html :

1. <ion-header>

2. <ion-navbar color="primary">

3. <ion-title *ngIf="movie">{{movie.title}}</ion-title>

4. </ion-navbar>

5. </ion-header>

7. <ion-content>

8. <ion-card *ngIf="movie">

9. <img [src]="movie.poster_path" />

10. <ion-card-header>

11. <h2>Synopsis

12. <ion-badge color="primary">{{movie.vote_average}}</ion-badge>

13. </h2>

14. </ion-card-header>

15. <ion-card-content>{{movie.overview}}</ion-card-content>

16. </ion-card>

17. </ion-content>

Pour afficher l'objet dans MovieDetailPage, il nous faudra le récupérer grâce à NavParam. On définit la valeur de la variable movie lors du lifecycle event ionViewDidLoad :

1. export class MovieDetailPage {

2. movie: IMovie;

4. constructor(

5. public navCtrl: NavController,

6. public navParams: NavParams

7. ) {}

9. ionViewDidLoad() {

10. this.movie = this.navParams.data;

11. }

12. }

Et voilà la première version de l'application avec une navigation du début à la fin :

Dans MovieListPage, nous avons directement initialisé notre liste en utilisant des valeurs fixes, mais ce n'est pas la meilleure chose à faire. On serait plutôt tenté de faire des appels à une WebAPI en utilisant le HttpClient d'Angular. Afin de respecter le SRP, nous allons déporter ces appels dans une autre classe et créer ce qu'Ionic appelle un provider, à rapprocher d'un service dans Angular.

VI-C - Création de notre premier provider

Ionic CLI nous permet de générer des providers assez simplement grâce à la commande ionic g provider movie-api. Cela génère le fichier src/app/providers/movie-api/movie-api.ts et met à jour AppModule en déclarant MovieApiProvider dans le tableau des providers, le mettant à disposition de tous les composants de ce module. Pour plus de détails sur les services/providers et sur l'utilisation du HttpClient, vous trouverez tout ce qu'il faut dans cet article. Je vous conseille de suivre pas à pas la déclaration du module http. Je passe directement à l'implémentation du provider :

1. import { Injectable } from "@angular/core";

2. import { HttpClient } from "@angular/common/http";

3. import { Platform } from "ionic-angular";

4. import { Observable } from "rxjs/Rx";

5. import { IMovie } from "../../interface/IMovie";

7. @Injectable()

8. export class MovieApiProvider {

9. private baseUrl: string = "../../assets/api/movies.json";

11. movies: IMovie[];

13. constructor(

14. private readonly http: HttpClient,

15. private readonly platform: Platform

16. ) {

17. console.log("Hello MovieApiProvider Provider");

18. }

20. getMovies(): Observable {

21. return this.http.get(`${this.baseUrl}`);

22. }

23. }

Ici, nous récupérons les données à partir d'un json, mais vous pouvez bien évidemment faire des appels REST.

Si vous testez l'application sur Android, vous aurez une erreur 404, car l'emplacement de movies.json ne sera pas le même. Pour remédier à cela, il faudra modifier le constructeur comme ceci :

1. constructor(

2. private readonly http: HttpClient,

3. private readonly platform: Platform

4. ) {

5. console.log("Hello MovieApiProvider Provider");

6. if (this.platform.is("cordova") && this.platform.is("android")) {

7. this.baseUrl = "/android_asset/www/assets/api/movies.json";

8. }

9. }

Une fois la méthode getMovies() créée, nous allons l'appeler dans MovieListPage et supprimer l'initialisation de notre variable movies :

1. export class MovieListPage {

2. movies  = new Array<Movie>();

4. constructor(

5. public navCtrl: NavController,

6. public navParams: NavParams,

7. private movieApiProvider: MovieApiProvider

8. ) {}

10. ionViewDidLoad() {

11. this.movieApiProvider.getMovies().subscribe(data =>{

12. this.movies = data;

13. })

14. }

16. goToDetail(movie: IMovie) {

17. this.navCtrl.push(MovieDetailPage, movie);

18. }

19. }

Nous avons bien évidemment utilisé le lifecycle event ionViewDidLoad pour initialiser notre variable movies à la création de la page. Vous devriez maintenant avoir une liste de films bien plus fournie que précédemment.

VI-D - Gestion des favoris

Le but est de choisir des films favoris à partir de la page de détail, et de les afficher sur la page de démarrage. Commençons par modifier movie-detail.html afin d'y afficher un bouton de favoris, que nous allons mettre sur la navbar à droite :

1. <ion-header>

2. <ion-navbar color="primary">

3. <ion-title *ngIf="movie">{{movie.title}}</ion-title>

4. <ion-buttons end>

5. <button ion-button incon-only style="font-size: 1.7em" (click)=toggleFavorite()>

6. <ion-icon [name]="isFavorite ? 'star':'star-outline'"></ion-icon>

7. </button>

8. </ion-buttons>

9. </ion-navbar>

10. </ion-header>

12. <-- suite de la page -->

Implémentons maintenant la méthode toggleFavorite() :

1. export class MovieDetailPage {

2. movie: IMovie;

3. isFavorite: boolean = false;

5. constructor(

6. public navCtrl: NavController,

7. public navParams: NavParams

8. ) {}

10. ionViewDidLoad() {

11. this.movie = this.navParams.data;

12. }

14. toggleFavorite() {

15. if (this.isFavorite) {

16. this.isFavorite = false;

17. // TODO persist data

18. } else {

19. this.isFavorite = true;

20. // TODO persist data

21. }

22. }

23. }

C'est une fonction qui permet d'inverser le booléen. Maintenant, le but est de stocker le choix de l'utilisateur. Pour cela nous allons utiliser Ionic Storage.

VII - Ionic storage

Le storage est un moyen facile de stocker des paires clé / valeur et des objets JSON. Storage utilise une variété de moteurs de stockage sous-jacents, en choisissant le meilleur disponible en fonction de la plate-forme.

Lorsqu'il est exécuté dans un contexte d'application native, Storage priorise l'utilisation de SQLite si (et seulement si) le plugin est présent dans l'application, car il s'agit d'une base de données stable et qui ne sera pas vidée en cas d'espace disque insuffisant sur le device.

Lorsqu'il s'exécute sur le Web ou sous la forme d'une application Web progressive, Storage tente d'utiliser IndexedDB, WebSQL et localstorage, dans cet ordre.

VII-A - Installation

Pour cela, nous allons nous baser sur la doc. Nous allons installer le plugin SQLite :
ionic cordova plugin add cordova-sqlite-storage

Ensuite nous allons ajouter IonicStorageModule.forRoot() à la liste d'imports de app.module.ts :

1. @NgModule({

2. declarations: [MyApp],

3. imports: [

4. BrowserModule,

5. IonicModule.forRoot(MyApp),

6. IonicStorageModule.forRoot(),

7. MyMoviesPageModule,

8. MovieListPageModule,

9. MovieDetailPageModule,

10. HttpClientModule

11. ],

12. bootstrap: [IonicApp],

13. entryComponents: [MyApp],

14. providers: [

15. StatusBar,

16. SplashScreen,

17. { provide: ErrorHandler, useClass: IonicErrorHandler },

18. MovieApiProvider

19. ]

20. })

21. export class AppModule {}

Maintenant que l'installation est finie, nous allons créer un provider pour y centraliser toutes les opérations de CRUD.

VII-B - Provider pour les opérations de CRUD

Sur le même principe que pour movie-api.ts, nous allons créer un provider favorite-movie.ts où l'on écrira toute la logique liée au stockage des films :

1. import { Injectable } from "@angular/core";

2. import { Storage } from "@ionic/storage";

3. import { IMovie } from "../../interface/IMovie";

5. const MOVIE_KEY = "movie_";

7. @Injectable()

8. export class FavoriteMovieProvider {

9. constructor(private storage: Storage) {

10. console.log("Hello UserPreferencesProvider Provider");

11. }

13. addFavoriteMovie(movie: IMovie) {

14. this.storage.set(this.getMovieKey(movie), JSON.stringify(movie));

15. }

17. removeFavoriteMovie(movie: IMovie) {

18. this.storage.remove(this.getMovieKey(movie));

19. }

21. isFavortieMovie(movie: IMovie) {

22. return this.storage.get(this.getMovieKey(movie));

23. }

25. toogleFavoriteMovie(movie: IMovie) {

26. this.isFavortieMovie(movie).then(

27. isFavorite =>

28. isFavorite

29. ? this.removeFavoriteMovie(movie)

30. : this.addFavoriteMovie(movie)

31. );

32. }

34. getMovieKey(movie: IMovie) {

35. return MOVIE_KEY + movie.id.toString();

36. }

38. getFavoriteMovies(): Promise<IMovie[]> {

39. return new Promise(resolve => {

40. let results: IMovie[] = [];

41. this.storage

42. .keys()

43. .then(keys =>

44. keys

45. .filter(key => key.includes(MOVIE_KEY))

46. .forEach(key =>

47. this.storage.get(key).then(data => results.push(JSON.parse(data)))

48. )

49. );

50. return resolve(results);

51. });

52. }

53. }

Tout d'abord, nous injectons dans notre constructeur le Storage qui nous permettra de stocker, récupérer et supprimer des données. Pour plus d'informations sur les méthodes et l'utilisation de Storage, je vous conseille de lire la doc qui est assez détaillée.

Maintenant que notre provider est prêt à gérer des données, utilisons-le.

VII-C - Application

Remettons-nous sur movie-detail.ts et améliorons ensemble la méthode toggleFavorite() en lui ajoutant la persistance des données. Il nous faut bien évidemment lui injecter FavoriteMovieProvider :

1. export class MovieDetailPage {

2. movie: IMovie;

3. favorite: boolean = false;

5. constructor(

6. public navCtrl: NavController,

7. public navParams: NavParams,

8. private favoriteMovieProvider: FavoriteMovieProvider

9. ) {}

11. ionViewDidLoad() {

12. this.movie = this.navParams.data;

13. this.favoriteMovieProvider

14. .isFavoriteMovie(this.movie.id)

15. .then(value => (this.favorite = value));

16. }

18. toggleFavorite(): void {

19. this.isFavorite = !this.isFavorite;

20. this.favoriteMovieProvider.toogleFavoriteMovie(this.movie);

21. }

22. }

La première chose que l'on fait lors du chargement de la page est d'appeler this.favoriteMovieProvider.isFavoriteMovie(movie). Cette méthode vérifie si l'élément se trouve en base ou pas. Si c'est le cas, cela veut dire que c'est un film de la liste des favoris de l'utilisateur et donc on set la valeur de this.favorite à true.
Pour ce qui est de la persistance des données, on fait simplement appel this.favoriteMovieProvider.toogleFavoriteMovie(this.movie), qui va gérer cela.

Maintenant que les données sont stockées, il faut les afficher sur la page de démarrage.

La question à se poser est : “Quel lifecycle event vais-je utiliser ?”
Eh non, ce n'est pas ionViewDidLoad(), car la root page n'est chargée (loaded) qu'une seule fois (sauf si l'on kill l'application) et elle sera toujours en bas de la pile (stack). Dans notre cas, on souhaite mettre à jour la liste des films favoris à chaque fois que l'on entre dans la page, on utilisera alors ionViewWillEnter().

Alimentons notre liste dans my-movies.ts

1. export class MyMoviesPage {

2. favoriteMovies: IMovie[] = [];

4. constructor(

5. public navCtrl: NavController,

6. public navParams: NavParams,

7. private favoriteMovieProvider: FavoriteMovieProvider

8. ) {}

10. ionViewDidLoad() {

11. console.log("ionViewDidLoad MyMoviesPage");

12. }

14. ionViewWillEnter() {

15. this.initFavoriteMovies();

16. }

18. private initFavoriteMovies() {

19. this.favoriteMovieProvider

20. .getFavoriteMovies()

21. .then(favs => (this.favoriteMovies = favs));

22. }

24. findMovie() {

25. this.navCtrl.push(MovieListPage);

26. }

28. goToDetail(movie: IMovie) {

29. this.navCtrl.push(MovieDetailPage, movie);

30. }

31. }

Il ne reste plus qu'à mettre à jour le template my-movies.html :

1. <br /><!-- Header ne change pas -->

3. <ion-content>

5. <ion-card *ngIf="favoriteMovies && favoriteMovies.length; else noFavorite">

6. <ion-list>

7. <ion-list-header class="my-movies-header">Favorite Movies</ion-list-header>

8. <ion-item *ngFor="let movie of favoriteMovies" (click)="goToDetail(movie)">

9. <ion-row>

10. <ion-col col-1>

11. <ion-icon name="star"></ion-icon>

12. </ion-col>

13. <ion-col col-6 text-wrap>

14. <h4>{{movie.title}}</h4>

15. </ion-col>

16. <ion-col col-3>

17. <h4>{{movie.release_date}}</h4>

18. </ion-col>

19. <ion-col col-2>

20. <ion-badge color="primary">{{movie.vote_average}}</ion-badge>

21. </ion-col>

22. </ion-row>

23. </ion-item>

24. </ion-list>

25. <ion-card-content>

26. <p>To choose more movies, click on this button.</p>

27. <button icon-left ion-button full (click)="findMovie()">

28. <ion-icon name="search"></ion-icon>

29. Find a movie

30. </button>

31. </ion-card-content>

32. </ion-card>

34. <ng-template #noFavorite>

35. <ion-card>

36. <ion-card-content>

37. <p>You haven't selected any movie</p>

38. <p>Please select one by using the button below</p>

39. <button ion-button icon-left full (click)="findMovie()">

40. <ion-icon name="search"></ion-icon>

41. Find a movie

42. </button>

43. </ion-card-content>

44. </ion-card>

45. </ng-template>

47. </ion-content>

J'ai utilisé ion-list pour afficher la liste des films, et voilà :

VIII - Conclusion

Dans ce tuto, nous avons développé une application Ionic from scratch. Nous avons appris à utiliser Ionic CLI pour la création de pages, la création de providers, le build, le lancement de l'appli… Nous avons passé en revue le principe de navigation utilisé dans Ionic et l'utilité des lifecycle events, et pour finir nous avons appris à utiliser les fonctionnalités de stockage d'Ionic.

Nous n'avons vu qu'une petite partie des possibilités de ce Framework. Je vous laisse aller plus loin, notamment en ajoutant une SearchBar, des Alerts, des Toasts et bien plus encore.

Pour rappel, le code lié à cet article est disponible dans son intégralité sur Github.

© SOAT
Toute reproduction interdite sans autorisation de l'auteur