View
4
Download
0
Category
Preview:
Citation preview
1
Angular 2 ROMAGNY13
Table des matières 1. EDI ............................................................................................................................ 4
2. TypeScript ................................................................................................................. 4
3. Création de projet ..................................................................................................... 4
a. En suivant le guide avec Webpack/ téléchargeant un starter-kit ............................ 4
b. Avec Angular CLI ................................................................................................... 5
« angular-cli.json » ..................................................................................................... 6
Génération de code ................................................................................................... 6
c. Quickstart avec System.js ....................................................................................... 8
4. Liens utiles ................................................................................................................ 8
5. Module Angular ......................................................................................................... 9
6. Component .............................................................................................................. 10
a. Styles .................................................................................................................... 11
b. ViewEncapsulation ................................................................................................ 11
c. Lifecyle hooks ....................................................................................................... 12
d. Relative paths (uniquement avec System.js) .......................................................... 12
7. Binding ..................................................................................................................... 12
a. String interpolations .............................................................................................. 13
a. Property binding ................................................................................................... 13
b. Event binding ....................................................................................................... 14
c. Two way binding ................................................................................................... 15
d. Nested component ................................................................................................ 15
e. Local reference ..................................................................................................... 17
8. Directives ................................................................................................................. 17
a. Attribute directives ............................................................................................... 17
ngClass .................................................................................................................... 17
ngStyle ..................................................................................................................... 17
b. Structural directives .............................................................................................. 17
ngIf ........................................................................................................................... 17
ngSwitch ................................................................................................................... 18
ngFor ........................................................................................................................ 18
9. Pipes (« équivalent » des filtres d’Angular 1) ............................................................ 19
a. Async pipe ............................................................................................................ 19
2
b. Custom pipe ......................................................................................................... 20
c. Autre exemple : Filtre ........................................................................................... 20
10. Debug (avec sourceMap) ...................................................................................... 21
Augury ......................................................................................................................... 21
11. Routing & navigation ............................................................................................. 22
a. Au plus simple ...................................................................................................... 22
b. Location stratégies ................................................................................................ 23
c. Passage de paramètre ........................................................................................... 23
d. Navigation par programmation ............................................................................. 24
e. Autres paramètres de routes ................................................................................. 24
f. Préserver les query params/ fragment quand on navigue vers une autre url ........ 26
g. Redirection ........................................................................................................... 26
h. Child routes .......................................................................................................... 26
i. RouterLinkActive ................................................................................................... 27
j. Route guards ......................................................................................................... 27
k. Mieux organiser ses routes ................................................................................... 28
l. Créer des modules pour le routing ....................................................................... 30
m. Lazy loading ...................................................................................................... 32
n. PreloadinStrategy ................................................................................................. 33
12. Services ................................................................................................................ 33
a. Dependency injection ........................................................................................... 33
b. http ....................................................................................................................... 34
Avec Observable...................................................................................................... 34
CRUD ....................................................................................................................... 36
Async pipe ............................................................................................................... 37
Avec promise ........................................................................................................... 37
13. Mean ..................................................................................................................... 38
14. Firebase ................................................................................................................ 40
15. Forms .................................................................................................................... 43
a. Template driven .................................................................................................... 43
b. Reactive forms ...................................................................................................... 44
16. Animations ............................................................................................................ 46
17. Test ....................................................................................................................... 46
a. Test de service ...................................................................................................... 46
b. Test de service http ............................................................................................... 47
c. Tester un component avec injection ...................................................................... 48
18. AOT (AHEAD-OF-TIME COMPILATION) ................................................................ 50
3
19. Déploiement ......................................................................................................... 50
20. Migration .............................................................................................................. 50
Upgrade d’un projet Angular 2 fait avec une ancienne version d’Angular Cli vers
Angular Cli 1.0.0 .......................................................................................................... 50
Site, Quickstart
4
1. EDI Visual Studio Code. Extensions : tsLint, snippets (peut être utile si on n’utilise pas les
commandes Angular-Cli) de J. Papa
WebStorm
2. TypeScript Angular 2 repose sur TypeScript. Ce document se concentre sur Angular 2. Quelques
bons liens quand même. En cours, on peut regarder du côté de Udemy, Pluralsight,
Egghead. La documentation officielle n’est pas forcément bien faite (la création de
fichiers de définition par exemple), il faudra parfois aller chercher les informations dans
les notes de mises à jour.
TypeScript permet notamment un typage fort. C’est un vrai apport par rapport à l’es6. Il
ne se contente bien entendu pas que de cela. Le code généré est également assez
propre.
Ici pas question de transpiler directement le TypeScript, on passera par l’intermédiaire
de Webpack. On peut utiliser un starter-kit ou Angular-Cli pour éviter de refaire la
configuration à chaque fois.
3. Création de projet
a. En suivant le guide avec Webpack/ téléchargeant un starter-kit Guide d'installation avec Webpack, NPM Packages
Starter kit avec Webpack (résultat du guide)
Installation des dépendances
npm i
Tests avec Jasmine
npm test Lancer le serveur de développement
npm start
… Se rendre à http://localhost:8080/
Build (avec Webpack)
npm run build
Seed project
5
b. Avec Angular CLI Site, github, référence, quickstart
Installation
npm i angular-cli -g
Ou pour avoir la dernière version
npm i angular-cli@latest -g
Création d’un projet
ng new <project-name> [options]
Options intéressantes : prefix (utilisé pour les components) exemple « --prefix abc » ce
qui donnerait en selector « abc-product-list »
Lancer le serveur de developpement
cd project-name
ng serve <project-name>
… Se rendre à « http://localhost:4200/ »
En cas de problème
Si une dépendance semble manquer, faire un « npm i »
Si on désire réinstaller carrément Angular-cli avec la dernière version
npm uninstall -g angular-cli
npm cache clean
npm install -g angular-cli@latest
Commandes
Aide : wiki,référence ,github du projet et commande :
ng help
6
Lint
ng lint
Tests (unitaires)
ng test [options]
E2e
ng e2e
Build (dossier « dist »)
ng build
ng build -prod
« angular-cli.json » On peut éditer directement le fichier de configuration d’Angular-Cli (à la racine du
porjet) et par exemple changer le préfixe par défaut des components
On peut également ajouter des styles (exemple avec Boostrap)
Note il y a quand même des Scripts NPM (se contentent de faire le lien avec les
commandes d’Angular-Cli)
npm start
npm test
npm e2e
Génération de code
wiki
ng g <type> [options]
Types valides :
module (ou m)
component (ou c)
directive
route
pipe
service (ou s)
class
interface
enum
7
Module Au plus simple
ng g m products
Note : le module n’est pas ajouté à AppModule, l’ajouter si pas de lazy loading
Options :
--spec true génére fichier de tests
--routing true
ng g m products --routing true
Component Au plus simple
ng g c products
Options
-it inline template
-is inline style
--flat pas de sous dossier crée pour le component
--spec false ne génére pas le fichier de tests du component
--prefix préfixe à utiliser
-ve ViewEncapsulation strategy
-cd change detection strategy
--skip-import
ng g c products/products -it -is --flat --spec false
Routing généré (forChild)
pour le module
Component avec « router-outlet »
Module
8
Exemple 2 : Component dans un sous dossier
ng g c products/product-list
Service
ng g s products/product
Ajouter le service aux providers du module
Options :
--spec false ne génére pas le fichier de tests du component
c. Quickstart avec System.js lien
4. Liens utiles Guide de bonnes pratiques (Style guide)
Cheat sheet
Observables rxjs, introduction rxjs, intro avec Angular
Getting started demos (J. Papa)
Deborah Kurata github (demos sur Angular 2, Reactive forms, etc.)
Pour créer des applications de démos :
mLab : création de base MongoDB (500mb free). Créer un compte, puis créer un
déploiement « single-node » « Sandbox »
Api :
Github : affichage des users et repositories. Créer une app OAuth (documentation)
Movies : créer un compte (documentation), OMDb API
Spotify : créer un compte (documentation)
Weather
9
5. Module Angular Documentation, faq
Un module Angular à la base c’est une classe avec un decorator « NgModule » :
import { NgModule } from '@angular/core';
@NgModule({
imports: [],
declarations: []
})
export class ProductModule { }
Un module peut avoir :
- imports (modules) Modules utilisés par ce module (exemple CommonModule pour les
directives ngIf, HttpModule pour des requêtes http, etc.)
- déclarations (components, directives et pipes). Par défaut les components, directives
et pipes sont « privés », accessibles seulement dans leur module
- exports (components, directives et pipes) rend les components, directives et pipes
exportés accessibles aux autres modules qui importerait ce même module.
- providers (services) : permettent d’enregistrer les services avec l’injector, ce qui les
rend disponible à toute l’application
Root application module (AppModule) :
peut être le seul module pour les petites applications
boostrap le root application component (AppComponent)
Exemple :
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';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Note : browserModule ne doit être utilisé qu’une fois (AppModule). Utiliser
CommonModule pour les autres modules.
Feature module (exemple un module « product ») : permet de mieux structurer
l’application (separation of concerns)
Shared module : déclarations (components, pipes et directives) + exports (modules
CommonModule , FormsModule par exemple) partagés (pas de providers, faire un core
Bootstrap AppComponent
AppComponent ajouté au tableau de
déclarations
Modules utilisés
par le module
Components à ajouter en
déclarations
10
module pour cela). Le module est déclaré dans les imports de « AppModule », ce qui
permet de partager les éléments à toute l’application.
Core module : services (providers) (et components) chargés (une seule fois) à
l’initialisation de l’application.
Route modules : permettent de structurer le routing.
6. Component Un component c’est une classe (properties + methods) avec un decorator @Component
(metadata) permettant de définir le template/ template url, etc.
Note : une bonne pratique consiste à préfixer les selectors des components (exemple
avec « app » ce qui donne « app-product-list ») pour éviter les éventuels conflits
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-list',
templateUrl: './product-list.component.html'
})
export class ProductListComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Avec les template on peut utiliser les backticks
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-list',
template: `<h1>Title</h1>
<p>Content.</P>`
})
export class ProductListComponent implements OnInit {
// etc.
}
Le component doit être ajouté aux déclarations du module (AppModule ou feature
module)
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductListComponent } from './product-list/product-
list.component';
@NgModule({
imports: [
CommonModule
],
declarations: [ProductListComponent]
})
export class ProductModule { }
11
a. Styles Documentation
« stylesUrls » tableau avec liens vers feuilles de styles
Ou « styles » (tableau de styles) (note : on peut utiliser les backticks pour définir dans
le tableau des styles sur plusieurs lignes)
b. ViewEncapsulation Documentation
None (styles « normaux »)
Exemple : Style ajouté dans le head
Native (shadow dom, pas supporté encore pas tous les navigateurs)
Style ajouté au component
Emulated
Style ajouté dans le « head »
Indiquée au niveau du component. Exemple
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<p [ngClass]="{ colorRed:true }">My text</p>
`,
encapsulation: ViewEncapsulation.Emulated,
styles: ['.colorRed { color: red }']
})
export class FirstComponent {}
12
c. Lifecyle hooks Documentation
OnChanges
OnInit
DoCheck
AfterContentInit
AfterContentChecked
AfterViewInit
AfterViewChecked
onDestroy
Exemple
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-product-list',
template: `
<h1>Title</h1>
`,
styles: []
})
export class ProductListComponent implements OnInit {
ngOnInit() {
}
}
d. Relative paths (uniquement avec System.js) Documentation
moduleId du decorator @component permet de simplifier les chemins des templates
et feuilles de styles des components.
Par exemple on pourra écrire simplement « ./first.component.html » au lieu de
« app/first.component.html »
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
templateUrl:'./first.component.html'
})
export class FirstComponent {}
7. Binding
{{}} => interpolation
[] => property binding
() => event binding
« Tout JavaScript valide » exemple opérateur ternaire, appel de function, etc.
Properties
Events
Component DOM
13
a. String interpolations import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<h1>{{ myTitle }}</h1>
<p>{{ myNumber }}</p>
`
})
export class FirstComponent {
myTitle: string = "My title";
myNumber: Number = 10;
}
a. Property binding
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<input type="text" [value]="myTitle"/>
<img [src]="myImage" />
`
})
export class FirstComponent {
myTitle: string = "My title";
myImage = "../assets/myimage.png";
}
Equivalent avec interpolation
Avec l’attribut « class »
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<p [class.colorRed]="true">My text</p>
`,
styles: ['.colorRed { color: red }']
})
export class FirstComponent {}
<img [src]="myImage" />
HTML Element attribute
(target)
Expression (source)
14
Avec l’attribut « style »
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<p [style.color]="'blue'">My text</p>
`
})
export class FirstComponent {}
b. Event binding
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<button (click)="onClick()">My button</button>
`
})
export class FirstComponent {
myTitle: string = "My title";
onClick() {
}
}
Passer l’event à la function
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<button (click)="onClick($event)">My button</button>
`
})
export class FirstComponent {
onClick(e) {
console.log(e);
}
}
Passer des données
Passer l’event + données
<button (click)="onClick()">My button</button>
Event (target) Méthode du component
15
c. Two way binding import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<input type="text" [(ngModel)]="person.name"/>
{{ person.name }}
<button (click)="changeName()">Change name</button>
`
})
export class FirstComponent {
person = {
name: "Marie"
};
changeName() {
this.person.name = "New Name";
}
}
Sans ngModel
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<input type="text" [value]="person.name"
(keyup)="person.name=$event.target.value"/>
`
})
export class FirstComponent {}
d. Nested component
Parent => child
Template de « AppComponent »
<div>
<app-parent></app-parent>
</div>
Parent
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child [result]="1000"></app-child>
`
})
export class ParentComponent {}
Obj => HTML Element
Parent component
Child
component Output
Input
16
Child
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<p>
{{ result }}
</p>
`,
styles: []
})
export class ChildComponent {
@Input() result: number = 10;
}
Affichera 1000 en sortie
(Les components parent et child sont à ajouter aux déclarations du module AppModule
pour l’exemple)
Child => parent
Child
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<button (click)="onClick()">Click!</button>
`
})
export class ChildComponent {
@Output() notified = new EventEmitter<string>();
onClick() {
this.notified.emit('to parent');
}
}
Parent
import { Component } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<app-child (notified)="onNotified($event)"></app-child>
`
})
export class ParentComponent {
onNotified(value: string) {
console.log('notified from child', value);
}
}
On s’abonne au custom event « notified » du
child. Lorsque l’on est notié on passe $even t (les
données (« to parent » dans l’exemple)
17
e. Local reference Sur un HTML element permet de définir une référence utilisable dans le html pour du
binding direct par rapport à cet élément
8. Directives
a. Attribute directives Sont appliquées avec [] comme le property binding, c’est pour cela qu’on les appelle
« attribute » directives.
Interagissent avec l’élément HTML sur lesquelles elles sont appliquées
ngClass (Equivalent de l’exemple avec attribut « class »)
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<p [ngClass]="{ colorRed:true }">My text</p>
`,
styles: ['.colorRed { color: red }']
})
export class FirstComponent {}
ngStyle (Equivalent de l’exemple avec attribut « style »)
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<p [ngStyle]="{ color:'blue' }">My text</p>
`
})
export class FirstComponent {}
b. Structural directives Changent le HTML/ la structure du DOM
ngIf Ajoute ou non selon la condition l’élément
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<div *ngIf="switch">Affiché si le switch est vrai</div>
`
})
export class FirstComponent {
switch = true;
}
La classe css est ajoutée selon la condition
(tout JavaScript valide)
Style inline
18
ngSwitch import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<div [ngSwitch]="value">
<p *ngSwitchCase="10">10</p>
<p *ngSwitchCase="100">10</p>
<p *ngSwitchDefault>Autre</p>
</div>
`
})
export class FirstComponent {
value = 10;
}
ngFor import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<ul>
<li *ngFor="let fruit of fruits">{{ fruit }}</li>
</ul>
`
})
export class FirstComponent {
fruits = ['banane', 'pêche', 'poire'];
}
Avec index
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<ul>
<li *ngFor='let fruit of fruits;let i = index'>{{ fruit }} - {{ i
}}</li>
</ul>
`
})
export class FirstComponent {
fruits = ['banane', 'pêche', 'poire'];
}
19
9. Pipes (« équivalent » des filtres d’Angular 1) Documentation
Permettent de transformer les données affichées
Built-in pipes :
uppercase
lowercase
date[ :format]
number[ :format]
async (subscribe à une promise ou un observable)
json
etc.
Exemple
import { Component } from '@angular/core';
@Component({
selector: 'app-first',
template: `
<div>
<h1>{{ title | uppercase }}</h1>
</div>
`
})
export class FirstComponent {
title: string = "My title";
}
On peut enchainer plusieurs pipes
{{ price | currency | lowercase }}
… et avoir des paramètres
{{ birthday | date:"MM/dd/yy" }}
{{ price | currency:'USD':true:'1.2-2' }}
a. Async pipe import { Component } from '@angular/core';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>{{ title | async }}</h1>
</div>
`
Min digits puis min fraction digits et enfin
max fraction digits
20
})
export class ProductListComponent {
title = new Promise((resolve) => {
setTimeout(() => resolve('product list'), 2000);
});
}
b. Custom pipe On peut générer facilement le code de base avec Angular-Cli (exemple avec une pipe
permet de mettre en capitale la première lettre d’une chaine de caractères nommée
« capitalize »)
ng g pipe capitalize
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'capitalize'
})
export class CapitalizePipe implements PipeTransform {
transform(value: any, args?: any): any {
return value.toLowerCase().replace(/^[a-z]/, (f) => f.toUpperCase());
}
}
Ajouter la pipe aux déclarations du module
Utilisation
<h1>{{ title | capitalize }}</h1>
c. Autre exemple : Filtre import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(value: any, filterBy?: string): any {
filterBy = filterBy ? filterBy.toLocaleLowerCase() : null;
return filterBy ? value.filter((item: any) =>
item.toLocaleLowerCase().indexOf(filterBy) !== -1) : value;
}
}
Utilisation
import { Component } from '@angular/core';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>Products</h1>
<input type="text" [(ngModel)]="listFilter">
<ul>
<li *ngFor="let fruit of fruits | filter:listFilter">{{ fruit }}</li>
</ul>
Value reçue à transformer + éventuels
paramètres passés (pour faire comme
DatePipe par exemple)
L’item pourrait être fortement
typé (product) et filtrer sur le
nom de product par exemple
21
</div>
`
})
export class ProductListComponent {
listFilter: string;
fruits = ['banane', 'pêche', 'poire'];
}
10. Debug (avec sourceMap)
Augury
Extension chrome
On peut debug le code TypeScript depuis
Chrome avec sourcemap activé (true) dans
le fichier de configuration de TypeScript
(tsConfig.json du dossier « src »)
22
11. Routing & navigation Documentation
a. Au plus simple
On a des components simples avec le minimum de code.
Template de l’AppComponent
<div>
<nav>
<a [routerLink]="['/']">Home</a>
<a [routerLink]="['/products']">Product list</a>
</nav>
<router-outlet></router-outlet>
</div>
On définit directement le routing dans l’AppModule :
import de RouterModule
définition des routes import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './product/product-list.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProductListComponent
],
imports: [
BrowserModule,
RouterModule.forRoot([
{ path: '', component: HomeComponent },
{ path: 'products', component: ProductListComponent }
])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
23
b. Location stratégies Documentation
2 stratégies :
path (par défaut) utilisation html5 history permettant d’avoir des uri propres sans hash
mais pas supporté pour tous les navigateurs (« pushState »)
hash avec # dans l’url. C’est l’ancienne méthode qui permet de faire des spa. (à gauche
de l’url ce qui est géré côté serveur, à droite ce qui est géré côté client et peut être
intercepté avec l’event « hashchange »)
Pour changer la stratégie
RouterModule.forRoot(APP_ROUTES, { useHash: true });
c. Passage de paramètre On ajoute une route acceptant un paramètre id dans AppModule (et on crée un nouveau
component ProductDetailComponent + ajout aux déclarations du module)
On définit un lien avec passage d’id (template de ProductListComponent par exemple)
Ou
24
Réception du paramètre de route
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-detail',
template: `
<p>{{ message }}</p>
`
})
export class ProductDetailComponent {
message: string;
constructor(private _route: ActivatedRoute) {
this.message = 'Product id:' + _route.snapshot.params['id'];
}
}
d. Navigation par programmation import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>Products</h1>
<button (click)="onNavigate()">Navigate</button>
</div>
`
})
export class ProductListComponent {
constructor(private _router: Router) { }
onNavigate() {
this._router.navigate(['/products', 1000]);
}
}
e. Autres paramètres de routes Lien avec query et fragment
Navigation par programmation
this._router.navigate(['/products', 1000], { queryParams: { q: 'xyz' },
fragment: 'section2' });
S’abonner aux observables
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-detail',
template: `
<p>{{ message }}</p>
<p>Id: {{ id }}</p>
<p>Recherche: {{ search }}</p>
<p>Section: {{ section }}</p>
`
})
Entourer le fragment de ‘’
Objet contenant queryParams
(object) et fragment (string)
25
export class ProductDetailComponent {
message: string;
id: string;
search: string;
section: string;
constructor(private _route: ActivatedRoute) {
this.message = 'Product id:' + _route.snapshot.params['id'];
_route.params.subscribe((param) => this.id = param['id']);
_route.queryParams.subscribe((queryParam) => this.search =
queryParam['q']);
_route.fragment.subscribe((fragment) => this.section = fragment);
}
}
Exemple d’url générée
Bonne pratique : se désabonner des observables on ngOnDestroy
Exemple
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Rx';
@Component({
selector: 'app-product-detail',
template: `
<p>Id: {{ id }}</p>
`
})
export class ProductDetailComponent implements OnDestroy {
id: string;
subscription: Subscription;
constructor(private _route: ActivatedRoute) {
this.subscription = _route.params.subscribe((param) => this.id =
param['id']);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
26
f. Préserver les query params/ fragment quand on navigue vers une autre url Exemple on ajout un lien dans le template de ProductDetailComponent
<a [routerLink]="['/']" [preserveQueryParams]="true"
[preserveFragment]="true">Home</a>
Par programmation
this._router.navigate(['/'], { preserveQueryParams: true,
preserveFragment: true });
g. Redirection Exemple redirrige vers « /products » depuis la page d’accueil
Ou défaut
Exemple redirection vers la page d’accueil
h. Child routes Pour mieux organiser son routing
Automatiquement « products »
est ajouté en début de chemins
ce qui donne « /products » et
« /products/ :id »
27
i. RouterLinkActive Exemple dans le template de AppComponent
CSS de AppComponent
routerLinkActiveOptions + exact permet de définir que la classe css est ajoutée si le
chemin correspond exactement. Sans cela par exemple pour la route « /products » ayant
des children, le lien serait toujours actif que l’on sur « /products » ou « products/ :id ».
Avec « exact » seul le chemin « /products » sera pris en compte pour l’ajout de la classe
CSS active.
j. Route guards Permet de contrôler l’activation et la désactivation d’une route. (Exemple avec une
simple boite de dialogue demandant la confirmation pour naviguer)
Création du guard
import { CanActivate, CanDeactivate, RouterStateSnapshot, ActivatedRouteSnapshot } from
'@angular/router';
import { Observable } from 'rxjs/Rx';
export interface ComponentCanDeactivate {
canDeactivate: () => boolean | Observable<boolean>;
}
export class ProductDetailGuard implements CanActivate,
CanDeactivate<ComponentCanDeactivate> {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):
Observable<boolean> | boolean {
return confirm('Navigate?');
}
canDeactivate(component: ComponentCanDeactivate): Observable<boolean> | boolean {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
Enregistrer le guard dans les providers du module
Code ajouté dans le component pour la désactivation
On pourrait par exemple demander à sauvegarder des données avant de quitter la page
import { Component} from '@angular/core';
import { Observable } from 'rxjs/Rx';
@Component({
selector: 'app-product-detail',
template: `
<p>Details</p>
`
})
export class ProductDetailComponent implements OnDestroy {
28
canDeactivate(): Observable<boolean> | boolean {
return confirm('Leave?');
}
}
(Penser à se désabonner avec ngOnDestroy si utilisation d’un observable)
Utilisation du guard sur une route (importer le guard)
Autre exemple : Autorisation
On vérifie par exemple que l’utilisateur est authentifié par accéder à la page sinon on le
redirige vers la page de login (en passant l’url vers laquelle il sera redirigé une fois
authentifié)
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (this.userProfileService.isLoggedIn) {
return true;
}
this.router.navigate(['/login'], { queryParams: { redirectTo: state.url } });
return false;
}
k. Mieux organiser ses routes
Child routes
Configuration du routing
Ajout du routing dans les imports d’AppModule
(+ Components dans les déclarations)
Importer le router
29
Fichier dédié au routing « app.routing.ts »
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { PRODUCT_ROUTES } from './product/product.routes';
const APP_ROUTES: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', children: PRODUCT_ROUTES },
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(APP_ROUTES);
Child routes « product.routes.ts »
import { Routes } from '@angular/router';
import { ProductListComponent } from './product-list.component';
import { ProductDetailComponent } from './product-detail.component';
export const PRODUCT_ROUTES: Routes = [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductDetailComponent }
];
« AppModule »
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { ProductListComponent } from './product/product-list.component';
import { ProductDetailComponent } from './product/product-
detail.component';
import { routing } from './app.routing';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
ProductListComponent,
ProductDetailComponent
],
imports: [
BrowserModule,
routing
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
30
l. Créer des modules pour le routing
ProductRoutingModule
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list.component';
import { ProductDetailComponent } from './product-detail.component';
const routes: Routes = [
{
path: 'products',
children: [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductDetailComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductRouterModule { }
export const routedComponents = [
ProductListComponent,
ProductDetailComponent
];
Feature module avec un
module de routing
(utilisant « forChild »)
AppModule avec un module de
routing AppRoutingModule +
ajout dans les imports du
feature module ProductModule
imports
imports
imports
31
ProductModule
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductRouterModule, routedComponents } from './product-
routing.module';
@NgModule({
imports: [
CommonModule,
ProductRouterModule
],
declarations: [routedComponents]
})
export class ProductModule { }
AppRoutingModule
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: '**', redirectTo: '' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
AppModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AppRoutingModule } from './app-routing.module';
import { ProductModule } from './product/product.module';
@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
BrowserModule,
ProductModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
32
m. Lazy loading AppRoutingModule
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', loadChildren: 'app/product/product.module#ProductModule' },
{ path: '**', redirectTo: '' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Modification ProductRoutingModule
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ProductListComponent } from './product-list.component';
import { ProductDetailComponent } from './product-detail.component';
const routes: Routes = [
{
path: '',
children: [
{ path: '', component: ProductListComponent },
{ path: ':id', component: ProductDetailComponent }
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ProductRouterModule { }
export const routedComponents = [
ProductListComponent,
ProductDetailComponent
];
AppModule
On supprime des imports « ProductModule »
On peut observer que le module ProductModule est chargé seulement à la demande/
quand on a besoin.
On définit une route avec un chemin vide
(« /products » étant désormais défini dans
AppRoutingModule) + les routes en children
33
n. PreloadinStrategy Documentation
12. Services
a. Dependency injection
Création d’un service. Exemple, générer le service « ProductService » avec Angular-Cli :
ng g service product/Product
import { Injectable } from '@angular/core';
import { Product } from './product';
@Injectable()
export class ProductService {
getProducts(): Product[] {
return [{ id: 1, name: 'Product 1' }, { id: 2, name: 'Product 2' }];
}
}
Injector
Component
constructor(private _myService : MyService){}
Injection du service
dans le constructor du
component
Service
export class MyService {}
34
On peut créer un model « Product »
export class Product {
id: number;
name: string;
}
Enregistrer le service dans le tableau de providers du module.
Utilisation du service (component, etc.)
import { Component, OnInit } from '@angular/core';
import { Product } from './product';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>Product list</h1>
<ul>
<li *ngFor="let product of products">{{ product.name }}</li>
</ul>
</div>
`
})
export class ProductListComponent implements OnInit {
products: Product[];
constructor(private _productService: ProductService) { }
ngOnInit() {
this.products = this._productService.getProducts();
}
}
b. http Documentation, headers
Ajouter au tableau d’ « imports » du module le module HttpModule
import { HttpModule } from '@angular/http';
Avec Observable Création d’un dossier api avec un fichier json de données d’exemple à la racine du
dossier « src »
{
"data":[
{
"id": 1,
"name": "Product 1"
},
{
"id": 2,
"name": "Product 2"
}
]
}
35
Modification du service
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import { Product } from './product';
@Injectable()
export class ProductService {
constructor(private http: Http) { }
getProducts(): Observable<Product[]> {
return this.http.get('api/products.json')
.map((response: Response) => <Product[]>response.json().data)
.do(data => console.log(data))
.catch(error => this.handleError(error));
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(`Error status code ${error.status} at
${error.url}`);
}
}
Component
import { Component, OnInit } from '@angular/core';
import { Product } from './product';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>Product list</h1>
<ul>
<li *ngFor="let product of products">{{ product.name }}</li>
</ul>
</div>
`
})
export class ProductListComponent implements OnInit {
products: Product[];
errorMessage: string;
constructor(private _productService: ProductService) { }
ngOnInit() {
this._productService.getProducts()
.subscribe(
products => this.products = products,
error => this.errorMessage = <any>error
);
Imports utilsés avec
l’observable retourné par la
méthode get de http
36
}
}
CRUD import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/toPromise';
import 'rxjs/add/observable/throw';
import { Post } from './post';
@Injectable()
export class PostService {
_headers: Headers;
_baseUrl: string;
constructor(private _http: Http) {
this._baseUrl = 'http://localhost:3000/api/v1/posts';
this._headers = new Headers({ 'Content-Type': 'application/json' });
}
getPosts(): Observable<Post[]> {
return this._http.get(this._baseUrl)
.map((response: Response) => <Post[]>response.json())
.do((data) => console.log(data))
.catch((error) => this.handleError(error));
}
getPost(id: any): any {
return this._http.get(`${this._baseUrl}/${id}`)
.map((response: Response) => <Post>response.json())
.do((data) => console.log(data))
.catch((error) => this.handleError(error));
}
addPost(post: Post): Observable<any> {
return this._http
.post(this._baseUrl, JSON.stringify(post), { headers: this._headers })
.map((response) => response.json())
.do((data) => console.log(data))
.catch((error) => this.handleError(error));
}
updatePost(id: any, post: Post): Observable<any> {
return this._http
.put(`${this._baseUrl}/${id}`, JSON.stringify(post), { headers: this._headers })
.map((response) => response.json())
.do((data) => console.log(data))
.catch((error) => this.handleError(error));
}
deletePost(id: any): Observable<any> {
return this._http.delete(`${this._baseUrl}/${id}`)
.map((response) => response.json())
.do((data) => console.log(data))
.catch((error) => this.handleError(error));
}
private handleError(error: Response) {
console.error(error);
return Observable.throw(`Error status code ${error.status} at ${error.url}`);
37
}
}
Async pipe import { Component, OnInit } from '@angular/core';
import { Product } from './product';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-list',
template: `
<div>
<h1>Product list</h1>
<ul>
<li *ngFor="let product of asyncProducts | async">{{ product.name
}}</li>
</ul>
</div>
`
})
export class ProductListComponent implements OnInit {
asyncProducts: any;
constructor(private _productService: ProductService) { }
ngOnInit() {
// async pipe
this.asyncProducts = this._productService.getProducts();
}
}
Avec promise Service
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import { Product } from './product';
@Injectable()
export class ProductService {
constructor(private http: Http) { }
getProductsP(): Promise<Product[]> {
return this.http.get('api/products.json') // returns an observable
.toPromise()
.then(response => {
return <Product[]>response.json().data;
})
.catch((error: any) => {
return Promise.reject('Cannot load products');
});
}
}
Component
this._productService.getProductsP()
.then(
products => this.products = products,
38
error => this.errorMessage = <any>error
);
13. Mean Créer un projet avec Angular-CLI
ng new <project-name>
Ajout des dépendances Node (Mongoose ou Mongojs)
npm i express mongojs body-parser ejs -S Créer le serveur
var express = require('express'),
path = require('path'),
bodyParser = require('body-parser'),
api = require('./routes/api');
var app = express();
// view engine
app.set('view engine', 'ejs');
app.engine('html', require('ejs').renderFile);
//
app.use(express.static(path.join(__dirname, 'dist')));
// body parser
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
// cors
app.use(function (req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS,
PUT, PATCH,DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-
With,content-type');
next();
});
// api
app.use('/api/v1/', api);
// render index page
app.get('*', function (req, res) {
res.sendFile(path.join(__dirname, 'dist/index.html'));
});
app.listen(3000, function () {
console.log('Server started on port 3000...');
});
Création des routes de l’Api (dans un dossier routes par exemple)
var express = require('express'),
router = express.Router(),
mongojs = require('mongojs');
var db = mongojs('mongodb://marie:marie@ds1259.mlab.com:61059/ blog',
['posts']);
// get http://localhost:3000/api/v1/posts
router.get('/posts', function (req, res, next) {
db.posts.find(function (err, posts) {
if (err) {
res.send(err);
39
} else {
res.json(posts);
}
});
});
// get one
router.get('/posts/:id', function (req, res, next) {
db.posts.findOne({
_id: mongojs.ObjectId(req.params.id)
}, function (err, post) {
if (err) {
res.send(err);
} else {
res.json(post);
}
});
});
// add
router.post('/posts', function (req, res, next) {
var post = req.body;
if (post.title === '' || post.content === '') {
res.status(400);
res.json({
"error": "Invalid data"
});
} else {
db.posts.save(post, function (err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
});
}
});
// update
router.put('/posts/:id', function (req, res, next) {
var post = req.body;
db.posts.update({
_id: mongojs.ObjectId(req.params.id)
}, post, {}, function (err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
});
});
// delete
router.delete('/posts/:id', function (req, res, next) {
db.posts.remove({
_id: mongojs.ObjectId(req.params.id)
}, '', function (err, result) {
if (err) {
res.send(err);
} else {
res.json(result);
}
});
});
module.exports = router;
40
14. Firebase Site. Il est possible d’avoir gratuit
Créer un projet / ou importer un projet Google existant
Dans l’onglet database il est possible de créer différentes collections
Il est possible également d’importer des données au format json.
Il est possible de changer les règles si on veut autoriser l’accès sans être connecté
Pour obtenir les identifiants, aller sur l’onglet « overview » et cliquer sur « Ajouter
firebase à votre application web »
Pour se connecter à sa base avec Angular.. Installer AngularFire2
npm i firebase angularfire2 -S
Dans AppModule ou un fichier à part
export const firebaseConfig = {
apiKey: 'AIzaSyAOZOCDwA35BCR6IEdnC-XfFQphk2zbikw',
41
authDomain: 'fb-project-b2857.firebaseapp.com',
databaseURL: 'https://fb-project-b2857.firebaseio.com',
storageBucket: 'fb-project-b2857.appspot.com'
};
Dans AppModule
Importer la configuration et le module
import { firebaseConfig } from './firebase.config';
import { AngularFireModule } from 'angularfire2';
… et les ajouter aux imports du module
imports: [
BrowserModule,
ReactiveFormsModule,
routing,
AngularFireModule.initializeApp(firebaseConfig)
],
Utilisation dans un service
import { Injectable } from '@angular/core';
import { AngularFire, FirebaseListObservable } from 'angularfire2';
import 'rxjs/add/operator/map';
import { Category } from './category';
import { Post } from './post';
@Injectable()
export class PostService {
_posts: FirebaseListObservable<Post[]>;
_categories: FirebaseListObservable<Category[]>;
constructor(private _angularFire: AngularFire) { }
getPosts(category?: any): FirebaseListObservable<Post[]> {
if (category !== null) {
this._posts = this._angularFire.database.list('posts', {
query: {
orderByChild: 'category',
equalTo: category
}
});
return this._posts;
} else {
this._posts = this._angularFire.database.list('posts');
return this._posts;
}
}
getPost(key: any) {
return this._angularFire.database.object(`/posts/${key}`);
}
addPost(post: Post): firebase.Promise<any> {
return this._posts.push(post);
}
updatePost(key: any, post: Post): firebase.Promise<any> {
return this._posts.update(key, post);
}
deletePost(key: any): firebase.Promise<any> {
42
return this._posts.remove(key);
}
getCategories() {
this._categories = this._angularFire.database.list('categories');
return this._categories;
}
}
Note : on utilse la clé générée ($key) par firebase que l’on ajoute à chaque model
Exemple le model Post
export class Post {
constructor(public $key: string,
public title: string,
public content: string,
public category: string) { }
}
Pour obtenir les résultats du service il suffit de souscrire aux observables (requêtes
GET) et aux promises pour les requêtes POST, PUT, DELETE.
Exemples
GET
ngOnInit() {
this._postService.getCategories().subscribe((categories) => {
this.categories = categories;
});
this._getPosts();
}
POST
onSubmit() {
if (this.form.valid) {
this._postService.addPost(this.form.value).then(() => {
this._router.navigate(['/posts']);
});
}
}
43
15. Forms Documentation, form validation
a. Template driven « Comme avec Angular 1 ». Se concentre sur le template
Ajouter au tableau d’ « imports » du module le module FormsModule
import { FormsModule } from '@angular/forms';
Directives : ngForm, ngModel, ngModelGroup
State et classes CSS (sur form et chaque html element):
- pristine/ng-pristine (non modifié), dirty/ng-dirty (modifié)
- valid/ng-valid, ng-invalid, errors (object)
- touched/ng-touched (visité), untouched/ng-untouched
<h1>Sign up</h1>
<form class="form-horizontal" (ngSubmit)="onSubmit(signupForm)" #signupForm="ngForm">
<fieldset>
<div class="form-group"
[ngClass]="{'has-error': (firstNameVar.touched || firstNameVar.dirty) &&
!firstNameVar.valid }">
<label class="col-md-2 control-label" for="firstNameId">First Name</label>
<div class="col-md-8">
<input class="form-control"
type="text"
id="firstNameId"
placeholder="First Name (required)"
required
minlength="3"
[(ngModel)]= "user.firstName"
name="firstName"
#firstNameVar="ngModel" />
<span class="help-block"
*ngIf="(firstNameVar.touched || firstNameVar.dirty) && firstNameVar.errors">
<span *ngIf="firstNameVar.errors.required">Please enter your first
name.</span>
<span *ngIf="firstNameVar.errors.minlength">The first name must be longer
than 3 characters.</span>
</span>
</div>
</div>
</fieldset>
<button type="submit"[disabled]="!signupForm.valid" class="btn btn-
primary">Submit</button>
</form>
Binding à l’objet user du component
Messages d’erreurs affichés selon l’erreur
Référence utilisée pour les messages d’erreurs
Classe css « has-error » ajoutée si le champ a été
visité ou modifié et qu’il a des erreurs
On passe la référence du
formulaire à la soumission
Bouton d’envoi grisé si le formulaire a des erreurs
Règles de validation permettant de
déterminer si le champ est valide (required,
minlength,maxlength,pattern)
44
Component
import { Component } from '@angular/core';
import { NgForm } from '@angular/forms';
@Component({
selector: 'app-signup',
templateUrl: './signup.component.html'
})
export class SignupComponent {
user = {
firstName: 'Marie',
email: 'marie@hotmail.com'
};
onSubmit(signupForm : NgForm) {
console.log(signupForm.form, signupForm.value);
}
}
b. Reactive forms Documentation
Se concentre d’avantage sur le component
Ajouter au tableau d’ « imports » du module le module ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms';
Directives :
formGroup
formControl
formControlName
formGroupName
formArrayName <h1>Sign up</h1>
<form class="form-horizontal" novalidate autocomplete="off" (ngSubmit)="onSubmit()"
[formGroup]="signupForm">
<fieldset>
<div class="form-group"
[ngClass]="{'has-error': (signupForm.get('firstName').touched ||
signupForm.get('firstName').dirty) && !signupForm.get('firstName').valid }">
<label class="col-md-2 control-label" for="firstNameId">First Name</label>
<div class="col-md-8">
<input class="form-control" id="firstNameId" type="text" placeholder="First
Name (required)" formControlName="firstName"/>
<span class="help-block" *ngIf="(signupForm.get('firstName').touched ||
signupForm.get('firstName').dirty) && signupForm.get('firstName').errors">
<span *ngIf="signupForm.get('firstName').errors.required">Please enter your
first name.</span>
<span *ngIf="signupForm.get('firstName').errors.minlength">The first name
must be longer than 3 characters.</span>
</span>
</div>
</div>
</fieldset>
<button type="submit" class="btn btn-primary"
[disabled]="!signupForm.valid">Submit</button>
</form>
Objet avec couples name/value (ou
groupes si utilisation de
ngModelGroup)
Permet de définir avec ngModel
des valeurs par défaut
45
Component
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-signup',
templateUrl: './signup.component.html'
})
export class SignupComponent implements OnInit {
signupForm: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.signupForm = this.fb.group({
'firstName': ['Marie', [Validators.required,
Validators.minLength(3)]]
}
onSubmit() {
console.log(this.signupForm, this.signupForm.value);
}
}
Sans FormBuilder
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-signup',
templateUrl: './signup.component.html',
styleUrls: ['./signup.component.css']
})
export class SignupComponent implements OnInit {
signupForm: FormGroup;
ngOnInit(): void {
// sans FormBuilder
this.signupForm = new FormGroup({
'firstName': new FormControl('Marie', [Validators.required,
Validators.minLength(3)])
});
}
onSubmit() {
console.log(this.signupForm, this.signupForm.value);
}
}
Built-in validators (documentation)
required
requiredTrue
minLength
maxLength
pattern
nullValidator
compose
composeAsync
Utilsation du form builder
qui permet de définir
facilement les FormGroup,
FormControls et validators
formControlName(Chaine de
caractères pour éviter les pb après
minification)
Utilisation de FormGroup (pour la
form ou groupes de controls) et
FormControl
Validators=functions qui sont appelées pour la validation,
ici on ne fait que les référencer (on ne les appelle pas)
Default value du formControl
(on peut laisser chaine vide)
Un seul validator ou un tableau
de validators
46
Ecouter les changements de statut du formulaire (VALID ou INVALID)
ngOnInit(): void {
this.signupForm = new FormGroup({
'firstName': new FormControl('Marie', [Validators.required,
Validators.minLength(3)])
});
this.signupForm.statusChanges.subscribe(
(data: any) => console.log(data)
);
}
16. Animations Documentation
17. Test Documentation, Jasmine, expectations, testing-angular-2-http-services-with-jasmine
a. Test de service import { Injectable } from '@angular/core';
@Injectable()
export class ProductService {
getData(): string {
return 'My data';
}
getDataAsync(): Promise<string> {
return new Promise<string>((resolve) => {
setTimeout(() => {
resolve('My async data');
}, 1000);
});
}
}
Test au plus simple
/* tslint:disable:no-unused-variable */
import { TestBed, async, inject } from '@angular/core/testing';
import { ProductService } from './product.service';
describe('ProductService', () => {
let service: ProductService;
beforeEach(() => {
service = new ProductService();
});
it('should get data', () => {
let result = service.getData();
expect(result).toEqual('My data');
});
it('should get async data', ((done) => {
service.getDataAsync().then((result) => {
expect(result).toEqual('My async data');
done();
});
}));
});
47
b. Test de service http
import { PostService } from './post.service';
import { Observable } from 'rxjs/Rx';
import { TestBed, async, inject, getTestBed } from '@angular/core/testing';
import {
Http,
Response,
ResponseOptions,
BaseRequestOptions
} from '@angular/http';
import { MockBackend } from '@angular/http/testing';
describe('PostService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
PostService,
MockBackend,
BaseRequestOptions,
{
provide: Http,
useFactory: (mockBackend, defaultOptions) => {
return new Http(mockBackend, defaultOptions);
},
deps: [MockBackend, BaseRequestOptions]
}
]
});
});
describe('Async tests', () => {
let postService: PostService;
let mockBackend;
beforeEach(inject([PostService, MockBackend], (_postService, _mockBa
ckend) => {
postService = _postService;
mockBackend = _mockBackend;
}));
it('should return an Observable', (done) => {
const mockResponse = {
data: [
{ id: 1, title: 'Post 1', content: 'Content 1' },
{ id: 2, title: 'Post 2', content: 'Content 2' },
{ id: 3, title: 'Post 3', content: 'Content 3' }
48
]
};
mockBackend.connections.subscribe((connection) => {
connection.mockRespond(new Response(new ResponseOptions({ bo
dy: mockResponse.data })));
});
postService.getPosts().subscribe((posts) => {
expect(posts.length).toBe(3);
expect(posts[0].title).toEqual('Post 1');
expect(posts[1].title).toEqual('Post 2');
expect(posts[2].title).toEqual('Post 3');
done();
});
});
});
});
c. Tester un component avec injection Le component utilisé
import { Component, OnInit } from '@angular/core';
import { ProductService } from './product.service';
@Component({
selector: 'app-product-list',
template: `
<p id="data">{{data}}</p>
<p id="async-data">{{asyncData}}</p>
`,
styles: []
})
export class ProductListComponent implements OnInit {
data: string;
asyncData: string;
constructor(private _productService: ProductService) { }
ngOnInit() {
this.data = this._productService.getData();
// async data
this._productService.getDataAsync().then((result) => {
this.asyncData = result;
});
}
}
49
Préparation
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { ProductListComponent } from './product-list.component';
import { ProductService } from './product.service';
describe('ProductListComponent', () => {
let component: ProductListComponent;
let fixture: ComponentFixture<ProductListComponent>;
let service: ProductService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ProductListComponent],
providers: [ProductService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProductListComponent);
component = fixture.componentInstance;
service = fixture.debugElement.injector.get(ProductService);
fixture.detectChanges();
});
/* tests */
});
Tester que le component récupère bien les données au chargement
it('should get data', () => {
expect(component.data).toBe('My data');
});
Tester que le component récupère bien les données async
it('should get async data', async(() => {
let spy = spyOn(service, 'getDataAsync')
.and.returnValue(Promise.resolve('My async data'));
fixture.whenStable().then(() => {
expect(component.asyncData).toBe('My async data');
});
}));
Tester que le component affiche bien les données
it('should display data', () => {
let compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('#data').textContent).toBe('My data');
});
Tester que le component affiche bien les données async
it('should display async data', async(() => {
let compiled = fixture.debugElement.nativeElement;
fixture.whenStable().then(() => {
expect(compiled.querySelector('#async-data').textContent).toBe('My
async data');
});
}));
50
18. AOT (AHEAD-OF-TIME COMPILATION) Documentation
19. Déploiement Documentation
20. Migration
Upgrade d’un projet Angular 2 fait avec une ancienne version d’Angular Cli vers Angular Cli 1.0.0 (On conserve le projet avec Angular 2)
a. Dans « package.json » remplacer en devDependencies:
"angular-cli": "1.0.0-beta.28.3",
… par
"@angular/cli": "1.0.0",
b. Renommer le fichier « angular-cli.json » en « .angular-cli.json »
c. Dans « .angular-cli.json » remplacer :
"environments": {
"source": "environments/environment.ts",
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
… par
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
Aide
Recommended