Créer une application single-page en Angular 2 Partie 2

Lire la première partie
Créer un service

Arrêtez de nouveau le process Angular avec Ctrl-C. Et exécutez la commande suivante :

ng g service Shared --spec false

 

Cette commande crée un service dans le fichier share.service.ts dans le dossier root module.

Remplacez le contenu du fichier shared.service.ls avec le code du listing 7 :

Listing 7. shared.service.ts

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

import { Injectable } from '@angular/core';

import { Http, Headers, Response } from "@angular/http";

import 'rxjs/Rx';

import { Observable } from "rxjs";

 

@Injectable()

export class SharedService {

    weatherURL1 = "https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22";

    weatherURL2 = "%2C%20";

    weatherURL3 = "%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys";

    findMovieURL1 = "http://www.omdbapi.com/?t=";

    findMovieURL2 = "&y=&plot=short&r=json";

    currencyURL = "http://api.fixer.io/latest?symbols=";

    totReqsMade: number = 0;

    constructor(private _http: Http) { }

 

    findWeather(city, state) {

        this.totReqsMade = this.totReqsMade + 1;

        return this._http.get(this.weatherURL1 + city + this.weatherURL2+ state + this.weatherURL3)

            .map(response => {

                { return response.json() };

            })

            .catch(error => Observable.throw(error.json()));

    }

 

    findMovie(movie) {

        this.totReqsMade = this.totReqsMade + 1;

        return this._http.get(this.findMovieURL1 + movie + this.findMovieURL2)

            .map(response => {

                { return response.json() };

            })

            .catch(error => Observable.throw(error.json().error));

    }

 

    getCurrencyExchRate(currency) {

        this.totReqsMade = this.totReqsMade + 1;

        return this._http.get(this.currencyURL + currency)

            .map(response => {

                { return response.json() };

            })

            .catch(error => Observable.throw(error.json()));

    }

}

Import dans listing 7 est parfait pour tous les services s’exécutant. @Injectable() est tout spécialement important, il indique que le service est injectable dans d’autres composants, une technique commune à l’injection de dépendance. 

La variable toReqsMade est déclarée ici et est utilisé pour passer les valeurs entre nos trois composants. Elle trace le nombre total de requêtes sur les services. XX

Nous avons trois méthodes, les noms qui explicitent : findWeather(), findMovie(), getCurrencyExchRate(). Dans l’exécution de la méthode, l’application “sort” du navigateur pour aller sur le web et consommer les microservices.

Faisons le lien entre les composants et les services créés.

Remplacez le code de move.component.ts par le listing 8 :

Listing 8. movie.component.ts

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

import { Component, OnInit } from '@angular/core';

import { SharedService } from "./../shared.service";

 

@Component({

  selector: 'app-movie',

  templateUrl: './movie.component.html',

  styles: []

})

export class MovieComponent implements OnInit {

    id_movie: string = "";

    mv_Title: string = "";

    mv_Rated: string = "";

    mv_Released: string = "";

    mv_Director: string = "";

    mv_Actors: string = "";

    mv_Plot: string = "";

    constructor(private _sharedService: SharedService) {

    }

 

    ngOnInit() {

    }

 

    callMovieService() {

        this._sharedService.findMovie(this.id_movie)

            .subscribe(

            lstresult => {

                this.mv_Title = lstresult["Title"];

                this.mv_Rated = lstresult["Rated"];

 

                this.mv_Released = lstresult["Released"];

                this.mv_Director = lstresult["Director"];

                this.mv_Actors = lstresult["Actors"];

                this.mv_Plot = lstresult["Plot"];

            },

            error => {

                console.log("Error. The findMovie result JSON value is as follows:");

                console.log(error);

            }

            );

    }

}

Le code appelle la méthode service pour donner les nouvelles données. Dans ce cas, il appelle callMovieService() et les appels utilisent la méthode this._sharedService.findMovie().

De la même manière, remplacez le code de currency.component.fs par le listing 9.

Listing 9. currency.component.ts

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

import { Component, OnInit } from '@angular/core';

import { SharedService } from "./../shared.service";

 

@Component({

  selector: 'app-currency',

  templateUrl: './currency.component.html',

  styles: []

})

export class CurrencyComponent implements OnInit {

  

  id_currency: string = "";

  my_result: any;

  constructor(private _sharedService: SharedService) {

  }

 

  ngOnInit() {

  }

 

  callCurrencyService() { 

    this._sharedService.getCurrencyExchRate(this.id_currency.toUpperCase())

      .subscribe(

      lstresult => {

                this.my_result = JSON.stringify(lstresult);

      },

      error => {

        console.log("Error. The callCurrencyService result JSON value is as follows:");

        console.log(error);

      }

      );

  }

}

Et remplacez le code de weather.component.ts par le listing 10 :

Listing 10. weather.component.ts

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 { Component, OnInit } from '@angular/core';

import { SharedService } from "./../shared.service";

 

@Component({

  selector: 'app-weather',

  templateUrl: './weather.component.html',

  styles: []

 

})

export class WeatherComponent implements OnInit {

  id_city: string = "";

  id_state: string = "";

  op_city: string = "";

  op_region: string = "";

  op_country: string = "";

  op_date: string = "";

  op_text: string = "";

  op_temp: string = "";

  constructor(private _sharedService: SharedService) {

  }

 

  ngOnInit() {

  }

 

  callWeatherService() {

    this._sharedService.findWeather(this.id_city, this.id_state)

      .subscribe(

      lstresult => {

        this.op_city = lstresult["query"]["results"]["channel"]["location"]["city"];

        this.op_region = lstresult["query"]["results"]["channel"]["location"]["region"];

        this.op_country = lstresult["query"]["results"]["channel"]["location"]["country"];

        this.op_date = lstresult["query"]["results"]["channel"]["item"]["condition"]["date"];

        this.op_text = lstresult["query"]["results"]["channel"]["item"]["condition"]["text"];

        this.op_temp = lstresult["query"]["results"]["channel"]["item"]["condition"]["temp"];

      },

      error => {

        console.log("Error. The findWeather result JSON value is as follows:");

        console.log(error);

      }

      );

  }

}

Maintenant, mettez à jour le module en incluant les services. Editez app.modul.ts pour inclure les états comme indiqué dans les lignes 12 et 28 du listing 11.

Listing 11. app.module.ts

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

import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

import { FormsModule } from '@angular/forms';

import { HttpModule } from '@angular/http';

 

import { AppComponent } from './app.component';

import { MenuComponent } from './menu.component';

import { WeatherComponent } from './weather/weather.component';

import { CurrencyComponent } from './currency/currency.component';

import { MovieComponent } from './movie/movie.component';

import { CONST_ROUTING } from './app.routing';

import { SharedService } from "./shared.service";

 

@NgModule({

  declarations: [

    AppComponent,

    MenuComponent,

    WeatherComponent,

    CurrencyComponent,

    MovieComponent

  ],

  imports: [

    BrowserModule,

    FormsModule,

    HttpModule,

    CONST_ROUTING

  ],

  providers: [SharedService],

  bootstrap: [AppComponent]

})

export class AppModule { }

Modifier la vue des modules

Nous voilà aux dernières pièces de notre puzzle. Vous avez besoin d’indiquer dans le code HTML l’appel aux bonnes méthodes services. Pour se faire, remplacez le code de movie.component.html par le code du our le  listing 12 :

Listing 12. movie.component.html

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

<h2>Open Movie Database</h2>

<div class="col-md-8 col-md-offset-2">

 <div class="form-group">

  <input type="text" required [(ngModel)]="id_movie" (change)="callMovieService()" class="form-control" placeholder="Enter Movie name ...">

  <br><br>

  <h3>Movie Details</h3>

  <br>

  <p class="well lead">

      <i> Title :</i> {{ this.mv_Title }} <br>

      <i> Plot :</i> {{ this.mv_Plot }} <br>

      <i> Actors :</i> {{ this.mv_Actors }} <br>

      <i> Directed by :</i> {{ this.mv_Director }} <br>

      <i> Rated :</i> {{ this.mv_Rated }} <br>

      <i> Release Date :</i> {{ this.mv_Released }} <br>

  </p>

  <p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :

      <span class="badge">{{this._sharedService.totReqsMade}}</span>

  </p>

 </div>

</div>

Plusieurs points sont à noter dans movie.component.html : 

  •       {{ this._sharedService.totReqsMade }}: valeur qui est suivi et partagé à travers les composants de l’application
  •        [(ngModel)]="id_movie": entrée saisie par l’utilisateur et transmise à la classe appelant ce code HTML. Dans ce cas, la classe est MovieComponent.
  •       (change)="callMovieService()": chaque fois que cette valeur change, vous demandez au système d’appeler la méthode callMovieService(), présente dans movie.component.ts
  •       {{ this.mv_Title }}, {{ this.mv_Plot }}, {{ this.mv_Actors }}, {{ this.mv_Director }}, {{ this.mv_Rated }}, {{ this.mv_Released }}: affiche les résultats du service appelé, depuis callMovieService() -> this._sharedService.findMovie(this.id_movie).

Ensuite, remplacez le code de weather.component.html par le listing 13 :

Listing 13. weather.component.html

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

<h2>Yahoo! Weather </h2>

<div class="col-md-8 col-md-offset-2">

 <div class="form-group">

  <input type="text" [(ngModel)]="id_city" class="form-control" placeholder="Enter City name ..."><br>

  <input type="text" [(ngModel)]="id_state" class="form-control" placeholder="Enter State. Example CA for California ..."><br>

  <button type="button" class="btn btn-primary" (click)="callWeatherService()">Submit</button>

  <br><br><br>

  <br>

  <p class="well lead">

    <i>City, State, Country :</i> {{ this.op_city }} {{ this.op_region }} {{ this.op_country }} <br>

    <i>Current Condition :</i> {{ this.op_text }} <br>

    <i>Current Temperature :</i> {{ this.op_temp }} <br>

  </p>

  <p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :

    <span class="badge">{{this._sharedService.totReqsMade}}</span>

  </p>

 </div>

</div>

Et enfin, remplacez le code de currency.component.html par le listing 14 :

Listing 14. currency.component.html

1

2

3

4

5

6

7

8

9

10

11

12

13

<h2>Currency Exchange Rates</h2>

<div class="col-md-8 col-md-offset-2">

 <div class="form-group">

  <input type="text" [(ngModel)]="id_currency" (change)="callCurrencyService()" class="form-control" placeholder="Enter Currency Symbol. Example: GBP(,AUD,INR)...">

  <br><br>

  <h3>Rate Details</h3>

  <br>

  <p class="well lead">Exchange rate relative to Euro in a JSON format: : {{ this.my_result }} </p>

  <p class="text-info">Total # of all the service requests including Weather, Movie, and Currency is :

    <span class="badge">{{this._sharedService.totReqsMade}}</span>

  </p>

 </div>

</div>

Maintenant, l’application peut accepter les entrées des utilisateurs, depuis le navigateur.

Exécuter l’application et améliorer l’interface !

L’application s’exécute et vous pouvez saisir des valeurs et afficher les résultats. Par exemple : cliquer sur Weather et saisir San Francisco, pour voir la météo de cette vive ville.

Test du nouveau skill Angular

Actuellement, les champs de saisies de notre application n’implémentent pas la validation et la gestion des erreurs. Vous pouvez ajouter ces contrôles. Astuce : ajouter les méthodes dans le service appelé validateMovie(movie-name), validateCurrency(currency-name), validateCity(city-name), et validateState(state-name). Puis appelez ces méthodes à partir des composants correspondants.

Tout fonctionne bien, mais l’interface pourrait être meilleure. Une des possibilités est de créer une belle interface en utilisant Bootstrap. 

Allez sur la documentation de Bootstrap et copier le code suivant :

1

2

<!-- Latest compiled and minified CSS -->

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

Ouvrez index.html et collez les lignes après la ligne 8 :

Listing 15. index.html

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

<!doctype html>

<html>

 

<head>

  <meta charset="utf-8">

  <title>DwNg2App</title>

  <base href="/">

 

  <!-- Latest compiled and minified CSS -->

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"

    crossorigin="anonymous">

 

  <meta name="viewport" content="width=device-width, initial-scale=1">

  <link rel="icon" type="image/x-icon" href="favicon.ico">

</head>

 

<body>

  <app-root>Loading...</app-root>

</body>

 

</html>

Maintenant, le look de l’application a changé, avec un stylet amélioré.

Modifier Index.html

Prenons quelques minutes pour comprendre pourquoi notre application est une SPA.

Quand l’application démarre, le serveur envoie index.html au navigateur et la page est le seul fichier que le navigateur affiche. Tout ce que fait Angular est d’insérer les contenus dans cette vue :

<app-root> à la fin d’index.html est remplacé par le contenu de app.component.html. Celui-ci contient <app-menu> and <router-outlet>. The <app-menu> remplit le contenu de menu.component.html et  <router-outlet> est rempli dynamiquement par le contenu de weather.component.html, currency.component.html, ou movie.component.html.

Tous ces sélecteurs sont statiques excepté la sélection réservé à Angular :  <router-outlet></router-outlet>. Il est rempli durant l’exécution et dépend de la valeur du routing.

Seul index.html est affiché et tous les autres fichiers html sont imbriqués à l’intérieur d’index.html.

Simuler le serveur

Notre application tourne sur notre PC de développement. Si vous voulez avoir accès à un serveur sandbox distant, vous devez migrer le code.

Vérifiez que Node.js et Angular CLI soient bien installés sur le sandbox distant. Compressez tout ce qui est possible dans le répertoire local du projet sauf le répertoire node_modules et son contenu. Copiez l’archive compressée sur le serveur sandbox et décompressez.

Allez sur le répertoire serveur contenant le package.json et exécutez npm install. package.json rend disponible la commande

Le fichier package.json permet à la commande npm install d’aller au référentiel public NPM et d’installer tous les packages nécessaires. Exécutez cette commande et le répertoire node_modules et son contenu seront créés automatiquement sur le serveur.

Exécutez la commande ng serve pour démarrer l’application dans la sandbox serveur, comme si vous étiez sur la machine de développement. Pour arrêter le process, ctrl-c.

Exécuter l’application avec ng serve —port sandbox-port# — host sandbox-hostname.

Maintenant l’application est disponible sur l’adresse : http://sandbox-hostname:sandbox-port#. Pour exécuter l’application sur votre navigateur de la machine de développement, arrêtez la sandbox serveur contenant l’application (ctrl-c).

Notez que l’application entière tourne sur le navigateur de votre ordinateur de développement même le process Angular sur le server est arrêté.

Vous comprenez que l’application est bien une SPA, que l’application tourne sur le navigateur et non sur le serveur et que l’application interroge le serveur seulement pour les nouvelles données. Que vous ayez 10 ou 1000 utilisateurs (donc 10 ou 1000 navigateurs) , cela n’aura pas d’impact sur le serveur.

Conclusion

Vous avez appris comment créer et faire tourner une application SPA en Angular 2 sur votre ordinateur et sur et la partie le serveur sandbox . Pour une production de l’application, posez la question à votre service IT pour les prérequis et les aspects de sécurité.

 

Plusieurs bonnes pratiques peuvent améliorer les performances :

- bundling : un process combinant plusieurs programmes en un seul fichier

- minifying : réduction de la taille des fichiers

- Ahead-of-time (AoT) compilation

 

Nous remercions Guy Huinen, Dean P Cummings, Don Turner et Mark Shade pour leurs conseils.