

Angular 4 : Pas à pas – Partie 2
Temps de lecture : 10 minutes

Pour ce qui est de la navigation, nous allons nous familiariser avec les différentes techniques de routage et à la fin de ce tutoriel nous créerons notre premier module Angular.
Services
Les services permettent de factoriser le code, pour respecter SRP (Single Responsability Principle). Le service sera donc injecté et utilisé dans ces composants. Angular CLI nous permet de générer un service grâce à la commandeng generate service products/service/product --flat -m app.module
…. Je vous vois ébahis, vos yeux grands écarquillés… Détaillons cette ligne de commande :
ng generate service products/service/product
: permet de générer le fichierproduct.service.ts
dans le dossier products/service/ ; notez que l’on peut remplacerng generate service
parng g s
--flat
: nous voulons queproduct.service.ts
soit à la racine du dossier products/service/. Sans cette option nous aurions euproducts/service/product/product.service.ts
-m app.module
: permet de déclarer le service dans le module spécifié, nous évite de le faire manuellement.

--spec
) et met à jour AppModule
en déclarant ProductService
dans le tableau des providers, le mettant à disposition de tous les composants de ce module.
Attention : Il faut se rappeler qu’une dépendance est par défaut un singleton dans la portée (scope) de l’injecteur. Lorsque nous déclarons notre service comme provider du module AppModule, il est donc un singleton pour toute l’application.Voici à quoi ressemble notre service :
import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
constructor() { }
}
@Injectable()
qui permet de faire savoir à Angular que cette classe peut être utilisée avec l’injection de dépendances.
Le service que nous venons de créer nous permettra de récupérer la liste des produits. On fera appel à notre service depuis ProductComponent
pour récupérer cette liste.
Nous allons créer la méthode getProducts() : IProduct[]
dans notre service :
Afin de l’utiliser dans notre composant, il suffit de le déclarer en private dans le constructor. En effet, TypeScript permet de créer des propriétés directement depuis les arguments du constructeur, en indiquant leur portée (import { Injectable } from "@angular/core"; @Injectable() export class ProductService { getProducts(): IProduct[] { return [ { productId: 1, productName: "Leaf Rake", productCode: "GDN-0011", releaseDate: "March 19, 2016", description: "Leaf rake with 48-inch wooden handle.", price: 19.95, starRating: 3.2, imageUrl: "http://openclipart.org/image/300px/svg_to_png/26215/Anonymous_Leaf_Rake.png" }, { productId: 2, productName: "Garden Cart", productCode: "GDN-0023", releaseDate: "March 18, 2016", description: "15 gallon capacity rolling garden cart", price: 32.99, starRating: 4.2, imageUrl: "http://openclipart.org/image/300px/svg_to_png/58471/garden_cart.png" } ]; } }
public
, protected
ou private
). Mais on peut aussi déclarer la propriété en dehors du constructeur et affecter la valeur dans le constructeur.Il est également conseillé de les déclarer en
readonly
pour indiquer que l’on ne compte pas changer sa valeur. Cela favorise la construction d’un objet complet, dans un état totalement défini, ce qui n’est pas le cas si l’on doit attendre que d’autres propriétés soient définies en dehors du constructeur :
Votre code est censé continuer de fonctionner. Nous avons déclaré une liste de produits en dur dans le service, qui sera récupérée dans notre composant afin d’être affichée, mais que faire si l’on souhaite récupérer nos données d’un fichier JSON ou bien d’une API ? Pas de panique, Angular nous met à disposition le module HttpClient et coup de bol incroyable, c’est ce que nous allons voir tout de suite ^^.constructor(private readonly _productService: ProductService) { this.products = this._productService.getProducts(); this.filteredProducts = this.products; }
HttpClient
Le module HttpClient est un module optionnel d’Angular qui vous permet de requêter vos API à l’aide du protocole HTTP. HttpClient supporte les verbes HTTP lors de l’exécution de vos requêtes. Le service HttpClient propose ainsi les méthodes get, post, put, delete, head et patch.
Nous allons commencer par déclarer le HttpClientModule dans notre AppModule comme suit :
Ensuite nous allons injecter l’HttpClient dans notre service et faire notre appel http :
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { IProduct } from "../product";
@Injectable()
export class ProductService {
private _productUrl = "./api/products/products.json";
constructor(private readonly _httpClient: HttpClient) {}
getProducts(): Observable<IProduct[]> {
return this._httpClient.get<IProduct[]>(this._productUrl);
}
}
Observable<IProduct[]>
. L’inférence du type à partir de httpClient.get<IProduct[]>()
suffit. Nous l’avons gardé pour faciliter la lecture du code.
Maintenant, il nous faut adapter notre ProductComponent pour souscrire à notre Observable :
constructor(private readonly _productService: ProductService) {
this._productService.getProducts().subscribe(
products => {
this.products = products;
this.filteredProducts = this.products;
}
);
}

angular-cli.json
:Il faudra killer la commande
ng serve
et la relancer pour que le changement soit pris en compte.
Le routage
L’un des rôles d’une application Web est de pouvoir rediriger vers des pages HTML au travers de liens HyperText. La balise responsable de ce routage est<a href="…">
. Lorsque l’on clique sur un lien HyperText, le navigateur intercepte cette action et va charger la nouvelle page. Il en résulte la perte de tout contexte JavaScript et un manque de fluidité de l’application.
Pour une application Angular, il n’est pas souhaitable de perdre tout son contexte à chaque changement de page. Angular a donc besoin d’un système de routage qui permet la navigation à travers différentes vues de l’application. Pour réaliser ce routage, Angular propose le module RouterModule disponible dans la librairie @angular/router
.
À chaque clic sur un lien ou à chaque changement d’url du navigateur, Angular router effectue ces sept étapes :
- Analyse l’url
- Applique les redirections
- Identifie les états du routeur
- Exécute les guards
- Détermine les données
- Active tous les composants nécessaires pour afficher la page
- Gère la navigation
Notre premier routage
Nous allons donc mettre en place un routage pour notre application :
@angular/router
, à savoir Routes et RouterModule:
Routes
est un tableau contenant la déclaration des routesRouterModule
est un module regroupant les directives et les services paramétrables permettant de remplir la fonctionnalité de routage.
L’ordre des éléments du tableau est important: si l’on met le wildcard ('**'
) en premier, toutes les URL seront redirigées vers la page d’accueil.
Lier les routes aux actions
Contrairement à une application Web classique, il est fastidieux d’utiliser l’attributhref
dans nos balises. Angular nous fournit donc la directive : routerLink. Cette directive prend en paramètre un tableau contenant un path et des paramètres optionnels (query parameters, fragment, etc.).Une fois nos liens déclarés, il est nécessaire d’indiquer à Angular où charger le contenu du lien. Cela se fait à l’aide de la directive router-outlet qui va accueillir le composant associé à la route. Nous allons donc modifier le Template du composant principal
AppComponent
:
<div>
<nav class='navbar navbar-default'>
<div class='container-fluid'>
<a class='navbar-brand'>{{pageTitle}}</a>
<ul class='nav navbar-nav'>
<li><a [routerLink]="['welcome']">Home</a></li>
<li><a [routerLink]="['products']">Product List</a></li>
</ul>
</div>
</nav>
<div class='container'>
<router-outlet></router-outlet>
</div>
</div>

Utilisation de paramètres de route
Que faire si l’on souhaite voir le détail d’un produit ? Il nous faut configurer une route qui prend l’id du produit comme paramètre et qui nous redirige vers la page contenant le détail du produit.Tout d’abord, nous allons créer
ProductDetailComponent
(en utilisant Angular CLI ng g c products/product-detail
) et ensuite nous allons modifier les routes :

import "rxjs/add/operator/map";
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import { IProduct } from "../product";
@Injectable()
export class ProductService {
private _productUrl = "./api/products/products.json";
constructor(private _httpClient: HttpClient) {}
getProducts(): Observable<IProduct[]> {
return this._httpClient.get<IProduct[]>(this._productUrl);
}
getProduct(id: number): Observable<IProduct> {
return this.getProducts().map((products: IProduct[]) =>
products.find(p => p.productId === id)
);
}
}
product-detail.component.html
:
<div class='panel panel-primary' *ngIf='product'>
<div class='panel-heading'>
{{pageTitle + ': ' + product.productName}}
</div>
<div class='panel-body'>
<div class='row'>
<div class='col-md-6'>
<div class='row'>
<div class='col-md-3'>Name:</div>
<div class='col-md-6'>{{product.productName}}</div>
</div>
<div class='row'>
<div class='col-md-3'>Code:</div>
<div class='col-md-6'>{{product.productCode | lowercase}}</div>
</div>
<div class='row'>
<div class='col-md-3'>Description:</div>
<div class='col-md-6'>{{product.description}}</div>
</div>
<div class='row'>
<div class='col-md-3'>Availability:</div>
<div class='col-md-6'>{{product.releaseDate}}</div>
</div>
<div class='row'>
<div class='col-md-3'>Price:</div>
<div class='col-md-6'>{{product.price|currency:'USD':true}}</div>
</div>
</div>
<div class='col-md-6'>
<img class='center-block img-responsive' [style.width.px]='200' [style.margin.px]='2' [src]='product.imageUrl' [title]='product.productName'>
</div>
</div>
</div>
<div class='panel-footer'>
<a class='btn btn-default' (click)='onBack()' style='width:80px'>
<i class='glyphicon glyphicon-chevron-left'></i> Back
</a>
</div>
</div>
ProductDetailComponent
:
import { Component, OnInit } from "@angular/core";
import { IProduct } from "../product";
import { ProductService } from "../service/product.service";
@Component({
templateUrl: "./product-detail.component.html",
styleUrls: ["./product-detail.component.css"]
})
export class ProductDetailComponent implements OnInit {
pageTitle: string = "Product Detail";
errorMessage: string;
product: IProduct;
constructor(
private _route: ActivatedRoute,
private _router: Router,
private _productService: ProductService
) {}
ngOnInit() {}
getProduct(id: number) {
this._productService
.getProduct(id)
.subscribe(
product => (this.product = product),
error => (this.errorMessage = <any>error)
);
}
onBack(): void {}
}
product
de type IProduct
; on instancie notre service dans le constructeur puis dans notre méthode getProduct
; on souscrit (subscribe) à notre Observable
.
Nous allons utiliser (encore une fois) la directive routerLink sur la liste des produits, mais cette fois nous lui passerons l’id du produit comme paramètre :
<td>
<a [routerLink]="['/products', product.productId]">
{{ product.productName }}
</a>
</td>
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { IProduct } from '../product';
import { ProductService } from '../service/product.service';
@Component({
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.css']
})
export class ProductDetailComponent implements OnInit {
pageTitle: string = 'Product Detail';
errorMessage: string;
product: IProduct;
constructor(private _route: ActivatedRoute,
private _router: Router,
private _productService: ProductService) {
}
ngOnInit() {
const id = +this._route.snapshot.paramMap.get('id');
this.getProduct(id);
}
getProduct(id: number) {
this._productService.getProduct(id).subscribe(
product =< this.product = product,
error =< this.errorMessage = <any<error);
}
onBack(): void {
}
}
this._route.snapshot.paramMap.get('id')
; le +
sert à convertir une chaîne de caractères en entier.La méthode
ngOnInit
fait partie du cycle de vie d’un composant Angular. Elle est appelée juste après la création du composant. C’est pour cela que nous y avons mis le code nécessaire à la récupération de notre produit, afin de charger le contenu à l’initialisation du composant.
Il nous reste la méthode onBack()
que nous n’avons pas encore implémentée… Je vous laisse le faire : vous aurez besoin d’utiliser la classe Router.
Et voilà :

Les modules
Un module Angular représente une classe contenant le décorateur@NgModule
. Son but est de mieux organiser les parties de l’application en les rangeant dans des blocs fonctionnels. Jetons un coup d’œil à l’architecture de notre application en utilisant compodoc :

ProductModule
en plus pour la partie produit. Il regroupera tous les composants ayant la même fonctionnalité logique.
Création de notre premier module
Nous allons utiliser Angular CLI afin de créer notre ProductModule : la commande est la suivante :ng g m products/product --flat -m app.module

Maintenant il nous faut :import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; @NgModule({ imports: [ CommonModule ], declarations: [] }) export class ProductModule { }
* Importer FormsModule et RouterModule
* Déclarer ProductListComponent et ProductDetailComponent
* Ajouter ProductService dans la liste des providers
* Créer les routes
Et l’on met à jour AppModule en supprimant ce que l’on a ajouté dans ProductModule :import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterModule, Routes } from '@angular/router'; import { ProductDetailComponent } from './product-detail/product-detail.component'; import { ProductListComponent } from './product-list/product-list.component'; import { ProductService } from './service/product.service'; const routes: Routes = [ { path: 'products', component: ProductListComponent }, { path: 'products/:id', component: ProductDetailComponent } ]; @NgModule({ imports: [ CommonModule, FormsModule, RouterModule.forChild(routes), ], declarations: [ ProductListComponent, ProductDetailComponent ], providers: [ProductService], }) export class ProductModule { }
Au niveau du routage, il y a une petite différence entre nos deux modules ; dans AppModule nous avons fait appel à la méthode statiqueimport { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule, Routes } from '@angular/router'; import { AppComponent } from './app.component'; import { WelcomeComponent } from './home/welcome.component'; import { ProductModule } from './products/product.module'; const routes: Routes = [ { path: 'welcome', component: WelcomeComponent }, { path: '', redirectTo: 'welcome', pathMatch: 'full' }, { path: '**', redirectTo: 'welcome', pathMatch: 'full' } ]; @NgModule({ declarations: [ AppComponent, WelcomeComponent, ], imports: [ BrowserModule, HttpClientModule, RouterModule.forRoot(routes), ProductModule ], bootstrap: [AppComponent] }) export class AppModule { }
RouterModule.forRoot
afin d’enregistrer les routes au niveau de l’application, dans un sous module nous utiliserons RouterModule.forChild
Appelez uniquementAprès ces modifications, voici à quoi ressemble l’architecture de notre application :RouterModule.forRoot
dans la racine AppModule. Dans tout autre module, vous devez appeler la méthodeRouterModule.forChild
pour enregistrer des routes supplémentaires.

Conclusion
Dans cette seconde partie, nous avons appris à créer des services réutilisables et injectables partout, et à utiliser le HttpClient d’Angular afin de faire des appels API. Nous nous sommes également familiarisés avec le routage d’Angular afin de naviguer dans notre SPA, et pour finir nous avons découpé notre application en deux modules distincts fonctionnellement. Si vous souhaitez aller plus loin, je vous conseille les cours de Deborah Kurata sur pluralsight ainsi que ceux de John Papa Pour rappel, le code lié à cet article est disponible dans son intégralité sur le GitHub de Soat. © SOATToute reproduction interdite sans autorisation de l’auteur
Nombre de vue : 1461
Merci pour cet article, simple à comprendre, concis et va à l’essentiel. Bravo !
Merci Farid, pour ce commentaire. Cela me fait plaisir et me motive à écrire d’autres articles.
Très bon article,Lamine
Merci Lamine, franchement rien à dire chapeau 🙂
en ce moment que ça bouge pas mal angular y’a 5 mois j’ai commencé en angular 2 et là la version 5 est déjà sortie.
Je suis vraiment ravi que cet article vous plaise.
@Rabii c’est vrai qu’il y a eu beaucoup de changement sur la scène Angular. Les versions beta d’Angular 2 était vraiment différentes entre elles, beaucoup trop de changement qui en ont dissuadé plus d’un. Aujourd’hui le framework semble se stabiliser, les changements sont “plus doux”, la base est la même. Donc ne t’inquiète pas, si tu peux coder en Angular 4 tu n’auras pas de soucis à te faire en Angular 5. Si ça peut d’intéresser, voici un peu de [lecture](https://blog.angular.io/version-5-0-0-of-angular-now-available-37e414935ced)
tu as utilisé des classes bootstrap. je pense qu’il manque peut être un détail dans cette partie qui permet d’avoir le même resultat au niveau design: c’est d’indiquer dans le fichier index.html :
1) lien vers bootstrap,
2) et la class=’container’,
au niveau du body
Bonjour Lamine,
le commentaire que j’ai posté concerne la première partie(c’était un détail).
pour cette deuxième partie rien à dire. merci beaucoup.
Hello hello 🙂
Ah j’aime bien quand il n’y a rien à dire :D. En tout cas merci à toi d’avoir pris le temps de suivre ce tuto et de m’avoir fait part de tes retours. Pour ce qui est de la déclaration de boostrap, je ne l’ai effectivement pas expliqué, en fait tu as plusieurs façons de l’inclure à ton projet. Il faut d’abord rajouter la dépendance à ton “package.json” et ensuite rajouté le path dans le tableau “styles” du fichier de configuration d’Angular CLI qui se trouve à la racine de ton projet “.angular-cli.json” c’est ce que j’ai fait sur le projet qui se trouve sur Github (https://github.com/SoatGroup/angular-project/blob/master/.angular-cli.json).
Pour voir l’autre façon de faire c’est par ici => https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/include-bootstrap.md
Totalement novice sur Angular, j’ai sorti la lampe frontale pour arpenter ton tutoriel.
Le constat est sans appel, j’ai la sensation maintenant de pouvoir aborder n’importe quel projet sereinement, d’adopter les bons reflexes et d’être déjà operationnel.
Merci pour ton temps et ta pédagogie !
Bonjour Lamine,
L’article est très bien, la traduction très correcte.
C’est justement là le soucis, vous auriez pu dire que vous vous êtes inspiré TRES FORTEMENT (pour rester correct), du cours de Deborah Kurata sur pluralsight.com.
Le lien est disponible lorsqu’on est inscrit, mais voilà son Github https://github.com/deborahk/angular-gettingstarted
Vous n’avez même pas pris la peine de changer les données de tests.
J’ai rien contre le fait de s’inspirer des travaux des autres, surtout qu’ici l’intention, faire grandir la communauté est la bonne, et je ne remets absolument pas en doutes vos compétences, mais donnez au moins vos sources.
Bien cordialement,