Les ressources numériques de la formation « Angular : Développez votre première application avec TypeScript » se trouve bien sur cette page ! 🙂

Vous trouverez, comme promis :
- 1. Les extraits de code
- Chapitre 1 : Premiers pas avec Angular
- Chapitre 2 : Les Composants
- Chapitre 3 : Les Templates
- Chapitre 4 : Les Directives
- Chapitre 5 : Les Pipes
- Chapitre 6 : Les Routes
- Chapitre 7 : Les Modules
- Chapitre 8 : Les Services
- Chapitre 9 : Les Formulaires
- Chapitre 10 : Effectuer des requêtes HTTP standards
- Chapitre 11 : Effectuer des traitements asynchrones avec RxJS
- Chapitre 12 : Authentification
- Chapitre 13 : Déployer votre application
- 2. Les questionnaires
- 3. La correction du code
- 4. L’application en ligne
Pré-requis : Au niveau des pré-requis pour Angular 8, vérifiez que vous avez Node 10 d’installé sur votre machine, avec la commande node -v. C’est tout !
1. Les extraits de code
Chapitre 1 : Premiers pas avec Angular
app.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>`, }) export class AppComponent { name = 'Angular'; }
app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
bs-config.json
{ "server": { "baseDir": "src", "routes": { "/node_modules": "node_modules" } } }
index.html
<!DOCTYPE html> <html> <head> <title>Angular QuickStart</title> <base href="/"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Polyfill(s) for older browsers --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </head> <body> <my-app>Loading AppComponent content here ...</my-app> </body> </html>
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; platformBrowserDynamic().bootstrapModule(AppModule).catch(err => console.log(err));
package.json (Angular v8)
{ "name": "ng-pokemons-app", "version": "1.0.0", "description": "An awesome application to handle some pokemons.", "scripts": { "build": "tsc -p src/", "build:watch": "tsc -p src/ -w", "serve": "lite-server -c=bs-config.json", "prestart": "npm run build", "start": "concurrently \"npm run build:watch\" \"npm run serve\"" }, "private": true, "dependencies": { "@angular/animations": "~8.0.0", "@angular/common": "~8.0.0", "@angular/compiler": "~8.0.0", "@angular/core": "~8.0.0", "@angular/forms": "~8.0.0", "@angular/platform-browser": "~8.0.0", "@angular/platform-browser-dynamic": "~8.0.0", "@angular/router": "~8.0.0", "core-js": "~2.5.4", "rxjs": "~6.4.0", "rxjs-compat": "~6.4.0", "systemjs": "~0.19.40", "zone.js": "~0.9.1" }, "devDependencies": { "angular-in-memory-web-api": "~0.8.0", "concurrently": "~3.6.0", "firebase-tools": "~7.0.2", "lite-server": "~2.4.0", "tslint": "~5.9.1", "typescript": "~3.4.0" } }
package.json (Angular v9)
{ "name": "ng-pokemons-app", "version": "1.0.0", "description": "An awesome application to handle some pokemons.", "scripts": { "build": "tsc -p src/", "build:watch": "tsc -p src/ -w", "serve": "lite-server -c=bs-config.json", "prestart": "npm run build", "start": "concurrently \"npm run build:watch\" \"npm run serve\"" }, "private": true, "dependencies": { "@angular/animations": "~9.0.5", "@angular/common": "~9.0.5", "@angular/compiler": "~9.0.5", "@angular/core": "~9.0.5", "@angular/forms": "~9.0.5", "@angular/platform-browser": "~9.0.5", "@angular/platform-browser-dynamic": "~9.0.5", "@angular/router": "~9.0.5", "core-js": "~3.6.4", "rxjs": "~6.5.4", "rxjs-compat": "~6.5.4", "systemjs": "~0.19.47", "zone.js": "~0.10.2" }, "devDependencies": { "angular-in-memory-web-api": "~0.9.0", "concurrently": "~5.1.0", "firebase-tools": "~7.14.0", "lite-server": "~2.5.4", "tslint": "~6.0.0", "typescript": "~3.8.3" } }
systemjs.config.js
(function (global) { System.config({ paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { // our app is within the dist/app folder 'app': 'app', // angular bundles '@angular/core': 'npm:@angular/core/bundles/core.umd.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js', '@angular/http': 'npm:@angular/common/bundles/common-http.umd.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', // other libraries 'tslib': 'npm:tslib/tslib.js', 'rxjs': 'npm:rxjs', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js' }, // packages tells the System loader how to load when no filename and/or no extension packages: { app: { defaultExtension: 'js', }, rxjs: { main: 'index.js', defaultExtension: 'js' }, 'rxjs/operators': { main: 'index.js', defaultExtension: 'js' } } }); })(this);
tsconfig.json
{ "compilerOptions": { "target": "es5", "module": "commonjs", "moduleResolution": "node", "sourceMap": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "lib": [ "es2015", "dom" ], "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "typeRoots" : ["node_modules/@types"] } }
Chapitre 2 : Les Composants
app.component.ts
import { Component, OnInit } from '@angular/core'; import { Pokemon } from './pokemon'; import { POKEMONS } from './mock-pokemons'; @Component({ selector: 'pokemon-app', template: '<h1>Pokémons</h1>' }) export class AppComponent implements OnInit { pokemons: Pokemon[] = null; ngOnInit() { this.pokemons = POKEMONS; } selectPokemon(pokemon: Pokemon) { console.log('Vous avez selectionné ' + pokemon.name); } }
mock-pokemons.ts
import { Pokemon } from './pokemon'; export const POKEMONS: Pokemon[] = [ { id: 1, name: "Bulbizarre", hp: 25, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/001.png", types: ["Plante", "Poison"], created: new Date() }, { id: 2, name: "Salamèche", hp: 28, cp: 6, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/004.png", types: ["Feu"], created: new Date() }, { id: 3, name: "Carapuce", hp: 21, cp: 4, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/007.png", types: ["Eau"], created: new Date() }, { id: 4, name: "Aspicot", hp: 16, cp: 2, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/013.png", types: ["Insecte", "Poison"], created: new Date() }, { id: 5, name: "Roucool", hp: 30, cp: 7, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/016.png", types: ["Normal", "Vol"], created: new Date() }, { id: 6, name: "Rattata", hp: 18, cp: 6, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/019.png", types: ["Normal"], created: new Date() }, { id: 7, name: "Piafabec", hp: 14, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/021.png", types: ["Normal", "Vol"], created: new Date() }, { id: 8, name: "Abo", hp: 16, cp: 4, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/023.png", types: ["Poison"], created: new Date() }, { id: 9, name: "Pikachu", hp: 21, cp: 7, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/025.png", types: ["Electrik"], created: new Date() }, { id: 10, name: "Sabelette", hp: 19, cp: 3, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/027.png", types: ["Normal"], created: new Date() }, { id: 11, name: "Mélofée", hp: 25, cp: 5, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/035.png", types: ["Fée"], created: new Date() }, { id: 12, name: "Groupix", hp: 17, cp: 8, picture: "https://assets.pokemon.com/assets/cms2/img/pokedex/detail/037.png", types: ["Feu"], created: new Date() } ];
pokemon.ts
export class Pokemon { id: number; hp: number; cp: number; name: string; picture: string; types: Array<string>; created: Date; }
Chapitre 3 : Les Templates
app.component.html
<h1 class='center'>Pokémons</h1> <div class='container'> <div class="row"> <div *ngFor='let pokemon of pokemons' class="col s6 m4"> <div class="card horizontal" (click)="selectPokemon(pokemon)"> <div class="card-image"> <img [src]="pokemon.picture"> </div> <div class="card-stacked"> <div class="card-content"> <p>{{ pokemon.name }}</p> <p><small>{{ pokemon.created }}</small></p> </div> </div> </div> </div> </div> </div>
Chapitre 4 : Les Directives
border-card.directive.ts
import { Directive, ElementRef } from '@angular/core'; @Directive({ selector: '[pkmnBorderCard]' }) export class BorderCardDirective { constructor(private el: ElementRef) { this.setBorder('#f5f5f5'); this.setHeight(180); } private setBorder(color: string) { let border = 'solid 4px ' + color; this.el.nativeElement.style.border = border; } private setHeight(height: number) { this.el.nativeElement.style.height = height + 'px'; } }
Chapitre 5 : Les Pipes
app.component.html
<h1 class='center'>Pokémons</h1> <div class='container'> <div class="row"> <div *ngFor='let pokemon of pokemons' class="col s6 m4"> <div class="card horizontal" (click)="selectPokemon(pokemon)" pkmn-border-card> <div class="card-image"> <img [src]="pokemon.picture"> </div> <div class="card-stacked"> <div class="card-content"> <p>{{ pokemon.name }}</p> <!-- on ajoute le pipe pour les dates, avec le bon format : --> <p><small>{{ pokemon.created | date:"dd/MM/yyyy" }}</small></p> <!-- on utilise la directive ngFor pour afficher tous les types d'un pokémon donnée --> <span *ngFor='let type of pokemon.types' class="{{ type | pokemonTypeColor }}">{{ type }}</span> </div> </div> </div> </div> </div> </div>
pokemon-type-color.pipe.ts
import { Pipe, PipeTransform } from '@angular/core'; /* * Affiche la couleur correspondant au type du pokémon. * Prend en argument le type du pokémon. * Exemple d'utilisation: * {{ pokemon.type | pokemonTypeColor }} */ @Pipe({name: 'pokemonTypeColor'}) export class PokemonTypeColorPipe implements PipeTransform { transform(type: string): string { let color: string; switch (type) { case 'Feu': color = 'red lighten-1'; break; case 'Eau': color = 'blue lighten-1'; break; case 'Plante': color = 'green lighten-1'; break; case 'Insecte': color = 'brown lighten-1'; break; case 'Normal': color = 'grey lighten-3'; break; case 'Vol': color = 'blue lighten-3'; break; case 'Poison': color = 'deep-purple accent-1'; break; case 'Fée': color = 'pink lighten-4'; break; case 'Psy': color = 'deep-purple darken-2'; break; case 'Electrik': color = 'lime accent-1'; break; case 'Combat': color = 'deep-orange'; break; default: color = 'grey'; break; } return 'chip ' + color; } }
Chapitre 6 : Les Routes
app-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ListPokemonComponent } from './list-pokemon.component'; import { DetailPokemonComponent } from './detail-pokemon.component'; // routes const appRoutes: Routes = [ { path: 'pokemons', component: ListPokemonComponent }, { path: 'pokemon/:id', component: DetailPokemonComponent }, { path: '', redirectTo: 'pokemons', pathMatch: 'full' } ]; @NgModule({ imports: [ RouterModule.forRoot(appRoutes) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
app.component.html
<nav> <div class="nav-wrapper teal"> <a href="#" class="brand-logo center">pokemon-app</a> </div> </nav> <router-outlet></router-outlet>
app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { ListPokemonComponent } from './list-pokemon.component'; import { DetailPokemonComponent } from './detail-pokemon.component'; import { BorderCardDirective } from './border-card.directive'; import { PokemonTypeColorPipe } from './pokemon-type-color.pipe'; @NgModule({ imports: [ BrowserModule, AppRoutingModule ], declarations: [ AppComponent, BorderCardDirective, PokemonTypeColorPipe, ListPokemonComponent, DetailPokemonComponent ], bootstrap: [AppComponent] }) export class AppModule { }
detail-pokemon.component.html
<div *ngIf="pokemon" class="row"> <div class="col s12 m8 offset-m2"> <h2 class="header center">{{ pokemon.name }}</h2> <div class="card horizontal hoverable"> <div class="card-image"> <img [src]="pokemon.picture"> </div> <div class="card-stacked"> <div class="card-content"> <table class="bordered striped"> <tbody> <tr> <td>Nom</td> <td><strong>{{ pokemon.name }}</strong></td> </tr> <tr> <td>Points de vie</td> <td><strong>{{ pokemon.hp }}</strong></td> </tr> <tr> <td>Dégâts</td> <td><strong>{{ pokemon.cp }}</strong></td> </tr> <tr> <td>Types</td> <td> <span *ngFor="let type of pokemon.types" class="{{ type | pokemonTypeColor }}">{{ type }}</span> </td> </tr> <tr> <td>Date de création</td> <td><em>{{ pokemon.created | date:"dd/MM/yyyy" }}</em></td> </tr> </tbody> </table> </div> <div class="card-action"> <a (click)="goBack()">Retour</a> </div> </div> </div> </div> </div> <h4 *ngIf='!pokemon' class="center">Aucun pokémon à afficher !</h4>
detail-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router, Params } from '@angular/router'; import { Pokemon } from './pokemon'; import { POKEMONS } from './mock-pokemons'; @Component({ selector: 'detail-pokemon', templateUrl: './app/detail-pokemon.component.html' }) export class DetailPokemonComponent implements OnInit { pokemons: Pokemon[] = null; pokemon: Pokemon = null; constructor(private route: ActivatedRoute, private router: Router) {} ngOnInit(): void { this.pokemons = POKEMONS; let id = +this.route.snapshot.paramMap.get('id'); for (let i = 0; i < this.pokemons.length; i++) { if (this.pokemons[i].id == id) { this.pokemon = this.pokemons[i]; } } } goBack(): void { this.router.navigate(['/pokemons']); } }
list-pokemon.component.html
<h1 class='center'>Pokémons</h1> <div class='container'> <div class="row"> <div *ngFor='let pokemon of pokemons' class="col s6 m4"> <div class="card horizontal" (click)="selectPokemon(pokemon)" pkmn-border-card> <div class="card-image"> <img [src]="pokemon.picture"> </div> <div class="card-stacked"> <div class="card-content"> <p>{{ pokemon.name }}</p> <p><small>{{ pokemon.created | date:"dd/MM/yyyy" }}</small></p> <span *ngFor='let type of pokemon.types' class="{{ type | pokemonTypeColor }}">{{ type }}</span> </div> </div> </div> </div> </div> </div>
list-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { Pokemon } from './pokemon'; import { POKEMONS } from './mock-pokemons'; import { Router } from '@angular/router'; @Component({ selector: 'list-pokemon', templateUrl: './app/list-pokemon.component.html' }) export class ListPokemonComponent implements OnInit { pokemons: Pokemon[] = null; constructor(private router: Router) { } ngOnInit(): void { this.pokemons = POKEMONS; } selectPokemon(pokemon: Pokemon): void { console.log('Vous avez selectionné ' + pokemon.name); let link = ['/pokemon', pokemon.id]; this.router.navigate(link); } }
page-not-found.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'page-404', template: ` <div class='center'> <img src="http://assets.pokemon.com/assets/cms2/img/pokedex/full/035.png"/> <h1>Hey, cette page n'existe pas !</h1> <a routerLink="/pokemons" class="waves-effect waves-teal btn-flat"> Retourner à l' accueil </a> </div> ` }) export class PageNotFoundComponent { }
Chapitre 7 : Les Modules
app-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { PageNotFoundComponent } from './page-not-found.component'; const appRoutes: Routes = [ { path: '', redirectTo: 'pokemons', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(appRoutes) ], exports: [ RouterModule ] }) export class AppRoutingModule { }
app.module.ts
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { PokemonsModule } from './pokemons/pokemons.module'; import { AppComponent } from './app.component'; import { PageNotFoundComponent } from './page-not-found.component'; @NgModule({ imports: [ BrowserModule, PokemonsModule, // L'odre de chargement des modules est très important AppRoutingModule // pour l'ordre de déclaration des routes ! ], declarations: [ AppComponent, PageNotFoundComponent ], bootstrap: [AppComponent] }) export class AppModule { }
pokemons-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { ListPokemonComponent } from './list-pokemon.component'; import { DetailPokemonComponent } from './detail-pokemon.component'; // les routes du module Pokémon const pokemonsRoutes: Routes = [ { path: 'pokemons', component: ListPokemonComponent }, { path: 'pokemon/:id', component: DetailPokemonComponent } ]; @NgModule({ imports: [ RouterModule.forChild(pokemonsRoutes) ], exports: [ RouterModule ] }) export class PokemonRoutingModule { }
pokemons.module.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ListPokemonComponent } from './list-pokemon.component'; import { DetailPokemonComponent } from './detail-pokemon.component'; import { BorderCardDirective } from './border-card.directive'; import { PokemonTypeColorPipe } from './pokemon-type-color.pipe'; @NgModule({ imports: [ CommonModule ], declarations: [ ListPokemonComponent, DetailPokemonComponent, BorderCardDirective, PokemonTypeColorPipe ], providers: [] }) export class PokemonsModule { }
Chapitre 8 : Les Services
pokemons.service.ts
import { Injectable } from '@angular/core'; import { Pokemon } from './pokemon'; import { POKEMONS } from './mock-pokemons'; @Injectable() export class PokemonsService { // Retourne tous les pokémons getPokemons(): Pokemon[] { return POKEMONS; } // Retourne le pokémon avec l'identifiant passé en paramètre getPokemon(id: number): Pokemon { let pokemons = this.getPokemons(); for(let index = 0; index < pokemons.length; index++) { if(id === pokemons[index].id) { return pokemons[index]; } } } }
Chapitre 9 : Les Formulaires
edit-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; import { Pokemon } from './pokemon'; import { PokemonsService } from './pokemons.service'; @Component({ selector: 'edit-pokemon', template: ` <h2 class="header center">Editer {{ pokemon?.name }}</h2> <p class="center"> <img *ngIf="pokemon" [src]="pokemon.picture"/> </p> <pokemon-form [pokemon]="pokemon"></pokemon-form> `, }) export class EditPokemonComponent implements OnInit { pokemon: Pokemon = null; constructor( private route: ActivatedRoute, private pokemonsService: PokemonsService) {} ngOnInit(): void { let id = +this.route.snapshot.params['id']; this.pokemon = this.pokemonsService.getPokemon(id); } }
pokemon-form.component.css
.ng-valid[required], .ng-valid.required { border-left: 5px solid #42A948; /* bordure verte */ } .ng-invalid:not(form) { border-left: 5px solid #a94442; /* bordure rouge */ }
pokemon-form.component.html
<form *ngIf="pokemon" (ngSubmit)="onSubmit()" #pokemonForm="ngForm"> <div class="row"> <div class="col s8 offset-s2"> <div class="card-panel"> <!-- Pokemon name --> <div class="form-group"> <label for="name">Nom</label> <input type="text" class="form-control" id="name" required pattern="^[a-zA-Z0-9àéèç]{1,25}$" [(ngModel)]="pokemon.name" name="name" #name="ngModel"> <div [hidden]="name.valid || name.pristine" class="card-panel red accent-1"> Le nom du pokémon est requis (1-25). </div> </div> <!-- Pokemon hp --> <div class="form-group"> <label for="hp">Point de vie</label> <input type="number" class="form-control" id="hp" required pattern="^[0-9]{1,3}$" [(ngModel)]="pokemon.hp" name="hp" #hp="ngModel"> <div [hidden]="hp.valid || hp.pristine" class="card-panel red accent-1"> Les points de vie du pokémon sont compris entre 0 et 999. </div> </div> <!-- Pokemon cp --> <div class="form-group"> <label for="cp">Dégâts</label> <input type="number" class="form-control" id="cp" required pattern="^[0-9]{1,2}$" [(ngModel)]="pokemon.cp" name="cp" #cp="ngModel"> <div [hidden]="cp.valid || cp.pristine" class="card-panel red accent-1"> Les dégâts du pokémon sont compris entre 0 et 99. </div> </div> <!-- Pokemon types --> <form class="form-group"> <label for="types">Types</label> <p *ngFor="let type of types"> <label> <input type="checkbox" class="filled-in" id="{{ type }}" [value]="type" [checked]="hasType(type)" [disabled]="!isTypesValid(type)" (change)="selectType($event, type)"/> <span [attr.for]="type"> <div class="{{ type | pokemonTypeColor }}"> {{ type }} </div> </span> </label> </p> </form> <!-- Submit button --> <div class="divider"></div> <div class="section center"> <button type="submit" class="waves-effect waves-light btn" [disabled]="!pokemonForm.form.valid"> Valider</button> </div> </div> </div> </div> </form> <h3 *ngIf="!pokemon" class="center">Aucun pokémon à éditer...</h3>
pokemon-form.component.ts
import { Component, Input, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { PokemonsService } from './pokemons.service'; import { Pokemon } from './pokemon'; @Component({ selector: 'pokemon-form', templateUrl: './app/pokemons/pokemon-form.component.html', styleUrls: ['./app/pokemons/pokemon-form.component.css'] }) export class PokemonFormComponent implements OnInit { @Input() pokemon: Pokemon; // propriété d'entrée du composant types: Array<string>; // types disponibles pour un pokémon : 'Eau', 'Feu', etc ... constructor( private pokemonsService: PokemonsService, private router: Router) { } ngOnInit() { // Initialisation de la propriété types this.types = this.pokemonsService.getPokemonTypes(); } // Détermine si le type passé en paramètres appartient ou non au pokémon en cours d'édition. hasType(type: string): boolean { let index = this.pokemon.types.indexOf(type); if (index > -1) return true; return false; } // Méthode appelée lorsque l'utilisateur ajoute ou retire un type au pokémon en cours d'édition. selectType($event: any, type: string): void { let checked = $event.target.checked; if (checked) { this.pokemon.types.push(type); } else { let index = this.pokemon.types.indexOf(type); if (index > -1) { this.pokemon.types.splice(index, 1); } } } // Valide le nombre de types pour chaque pokémon isTypesValid(type: string): boolean { if (this.pokemon.types.length === 1 && this.hasType(type)) { return false; } if (this.pokemon.types.length >= 3 && !this.hasType(type)) { return false; } return true; } // La méthode appelée lorsque le formulaire est soumis. onSubmit(): void { console.log("Submit form !"); let link = ['/pokemon', this.pokemon.id]; this.router.navigate(link); } }
Chapitre 10 : Effectuer des requêtes HTTP standards
add-pokemon.component.html
<h2 class="header center">Ajouter un Pokémon</h2> <pokemon-form [pokemon]="pokemon"></pokemon-form>
add-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { Pokemon } from './pokemon'; @Component({ selector: 'add-pokemon', templateUrl: './app/pokemons/add-pokemon.component.html' }) export class AddPokemonComponent implements OnInit { pokemon: Pokemon = null; constructor() { } ngOnInit(): void { this.pokemon = new Pokemon(); } }
in-memory-data.service.ts
import { InMemoryDbService } from 'angular-in-memory-web-api'; import { POKEMONS } from './pokemons/mock-pokemons'; export class InMemoryDataService implements InMemoryDbService { createDb() { let pokemons = POKEMONS; return { pokemons }; } }
loader.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'pkmn-loader', template: ` <div class="preloader-wrapper big active"> <div class="spinner-layer spinner-blue"> <div class="circle-clipper left"> <div class="circle"></div> </div><div class="gap-patch"> <div class="circle"></div> </div><div class="circle-clipper right"> <div class="circle"></div> </div> </div> </div> ` }) export class LoaderComponent {}
search-pokemon.component.html
<div class="row"> <div class="col s12 m6 offset-m3"> <div class="card"> <div class="card-content"> <div class="input-field"> <input #searchBox (keyup)="search(searchBox.value)" placeholder="Rechercher un pokémon"/> </div> <div class="collection"> <a *ngFor="let pokemon of pokemons$ | async" (click)="gotoDetail(pokemon)" class="collection-item"> {{ pokemon.name }} </a> </div> </div> </div> </div> </div>
search-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { Observable, Subject, of } from 'rxjs'; import { PokemonsService } from './pokemons.service'; import { Pokemon } from './pokemon'; @Component({ selector: 'pokemon-search', templateUrl: './app/pokemons/search-pokemon.component.html' }) export class PokemonSearchComponent implements OnInit { private searchTerms = new Subject<string>(); pokemons$: Observable<Pokemon[]>; constructor( private pokemonsService: PokemonsService, private router: Router) { } // Ajoute un terme de recherche dans le flux de l'Observable 'searchTerms' search(term: string): void { this.searchTerms.next(term); } ngOnInit(): void { this.pokemons$ = this.searchTerms.pipe( // attendre 300ms de pause entre chaque requête debounceTime(300), // ignorer la recherche en cours si c'est la même que la précédente distinctUntilChanged(), // on retourne la liste des résultats correpsondant aux termes de la recherche switchMap((term: string) => this.pokemonsService.searchPokemons(term)), ); } gotoDetail(pokemon: Pokemon): void { let link = ['/pokemon', pokemon.id]; this.router.navigate(link); } }
Chapitre 11 : Effectuer des traitements asynchrones avec RxJS
loader.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'pkmn-loader', template: ` <div class="preloader-wrapper big active"> <div class="spinner-layer spinner-blue"> <div class="circle-clipper left"> <div class="circle"></div> </div><div class="gap-patch"> <div class="circle"></div> </div><div class="circle-clipper right"> <div class="circle"></div> </div> </div> </div> ` }) export class LoaderComponent {}
search-pokemon.component.html
<div class="row"> <div class="col s12 m6 offset-m3"> <div class="card"> <div class="card-content"> <div class="input-field"> <input #searchBox (keyup)="search(searchBox.value)" placeholder="Rechercher un pokémon"/> </div> <div class="collection"> <a *ngFor="let pokemon of pokemons$ | async" (click)="gotoDetail(pokemon)" class="collection-item"> {{ pokemon.name }} </a> </div> </div> </div> </div> </div>
search-pokemon.component.ts
import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators'; import { Observable, Subject, of } from 'rxjs'; import { PokemonsService } from './pokemons.service'; import { Pokemon } from './pokemon'; @Component({ selector: 'pokemon-search', templateUrl: './app/pokemons/search-pokemon.component.html' }) export class PokemonSearchComponent implements OnInit { private searchTerms = new Subject<string>(); pokemons$: Observable<Pokemon[]>; constructor( private pokemonsService: PokemonsService, private router: Router) { } // Ajoute un terme de recherche dans le flux de l'Observable 'searchTerms' search(term: string): void { this.searchTerms.next(term); } ngOnInit(): void { this.pokemons$ = this.searchTerms.pipe( // attendre 300ms de pause entre chaque requête debounceTime(300), // ignorer la recherche en cours si c'est la même que la précédente distinctUntilChanged(), // on retourne la liste des résultats correpsondant aux termes de la recherche switchMap((term: string) => this.pokemonsService.searchPokemons(term)), ); } gotoDetail(pokemon: Pokemon): void { let link = ['/pokemon', pokemon.id]; this.router.navigate(link); } }
Chapitre 12 : Authentification
auth-guard.service.ts
import { Injectable } from '@angular/core'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { let url: string = state.url; return this.checkLogin(url); } checkLogin(url: string): boolean { if (this.authService.isLoggedIn) { return true; } this.authService.redirectUrl = url; this.router.navigate(['/login']); return false; } }
auth.service.ts
import { Injectable } from '@angular/core'; // RxJS 6 import { Observable, of } from 'rxjs'; import { tap, delay } from 'rxjs/operators'; @Injectable() export class AuthService { isLoggedIn: boolean = false; // L'utilisateur est-il connecté ? redirectUrl: string; // où rediriger l'utilisateur après l'authentification ? // Une méthode de connexion login(name: string, password: string): Observable<boolean> { // Faites votre appel à un service d'authentification... let isLoggedIn = (name === 'pikachu' && password === 'pikachu'); return of(true).pipe( delay(1000), tap(val => this.isLoggedIn = isLoggedIn) ); } // Une méthode de déconnexion logout(): void { this.isLoggedIn = false; } }
login-routing.module.ts
import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AuthService } from './auth.service'; import { LoginComponent } from './login.component'; @NgModule({ imports: [ RouterModule.forChild([ { path: 'login', component: LoginComponent } ]) ], exports: [ RouterModule ], providers: [ AuthService ] }) export class LoginRoutingModule {}
login.component.ts
import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from './auth.service'; @Component({ selector: 'login', template: ` <div class='row'> <div class="col s12 m4 offset-m4"> <div class="card hoverable"> <div class="card-content center"> <span class="card-title">Page de connexion</span> <p><em>{{message}}</em></p> </div> <form #loginForm="ngForm"> <div> <label for="name">Name</label> <input type="text" id="name" [(ngModel)]="name" name="name" required> </div> <div> <label for="password">Password</label> <input type="password" id="password" [(ngModel)]="password" name="password" required> </div> </form> <div class="card-action center"> <a (click)="login()" class="waves-effect waves-light btn" *ngIf="!authService.isLoggedIn">Se connecter</a> <a (click)="logout()" *ngIf="authService.isLoggedIn">Se déconnecter</a> </div> </div> </div> </div> ` }) export class LoginComponent { message: string = 'Vous êtes déconnecté. (pikachu/pikachu)'; private name: string; private password: string; constructor(private authService: AuthService, private router: Router) { } // Informe l'utilisateur sur son authentfication. setMessage() { this.message = this.authService.isLoggedIn ? 'Vous êtes connecté.' : 'Identifiant ou mot de passe incorrect.'; } // Connecte l'utilisateur auprès du Guard login() { this.message = 'Tentative de connexion en cours ...'; this.authService.login(this.name, this.password).subscribe(() => { this.setMessage(); if (this.authService.isLoggedIn) { // Récupère l'URL de redirection depuis le service d'authentification // Si aucune redirection n'a été définis, redirige l'utilisateur vers la liste des pokemons. let redirect = this.authService.redirectUrl ? this.authService.redirectUrl : '/pokemon/all'; // Redirige l'utilisateur this.router.navigate([redirect]); } else { this.password = ''; } }); } // Déconnecte l'utilisateur logout() { this.authService.logout(); this.setMessage(); } }
Chapitre 13 : Déployer votre application
systemjs.config.server.js
/** * Version de SystemJS dédié à la production. * Les paquets sont chargés depuis le web. */ (function (global) { System.config({ paths: { 'npm:': 'https://unpkg.com/' // Le nouvel alias }, map: { app: 'dist', '@angular/core': 'npm:@angular/core@8.0.0/bundles/core.umd.min.js', '@angular/common': 'npm:@angular/common@8.0.0/bundles/common.umd.min.js', '@angular/compiler': 'npm:@angular/compiler@8.0.0/bundles/compiler.umd.min.js', '@angular/platform-browser': 'npm:@angular/platform-browser@8.0.0/bundles/platform-browser.umd.min.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@8.0.0/bundles/platform-browser-dynamic.umd.min.js', '@angular/common/http': 'npm:@angular/common@8.0.0/bundles/common-http.umd.min.js', '@angular/router': 'npm:@angular/router@8.0.0/bundles/router.umd.min.js', '@angular/forms': 'npm:@angular/forms@8.0.0/bundles/forms.umd.min.js', 'rxjs': 'npm:rxjs@6.2.0', 'angular-in-memory-web-api': 'npm:angular-in-memory-web-api@0.8.0/bundles/in-memory-web-api.umd.js', 'tslib': 'npm:tslib/tslib.js' }, packages: { app: { main: './main.js', defaultExtension: 'js' }, rxjs: { main: 'index.js', defaultExtension: 'js' }, 'rxjs/operators': { main: 'index.js', defaultExtension: 'js' } } }); })(this);
2. Les questionnaires
- Questionnaire débutants : bit.ly/learn-angular-quiz-1
- Questionnaire intérmédiaires : bit.ly/learn-angular-quiz-2
- Questionnaire experts : bit.ly/learn-angular-quiz-3
3. La correction du code
L’ensemble du code développé de l’application de Pokémons développé pendant la formation peut être téléchargé en cliquant sur le bouton ci-dessous :
4. L’application en ligne
L’application de démonstration que vous réaliserez pendant la formation est accessible en ligne : https://ng8-pokemons-app.firebaseapp.com.
Bon apprentissage à toutes et à tous! 💻👏
Une remarque ou une suggestion à propos de la formation ?
Contactez-moi directement via le formulaire de contact, je recevrai votre message directement dans ma boîte mail personnelle.
Je répond généralement dans la journée. 🙂
Laisser un commentaire