RESSOURCES

Apprendre Angular

Aux utilisateurs de Udemy : 

« Angular évolue constamment, comme beaucoup de technologies dans le monde du développement Web (une nouvelle version majeure tout les 6 mois !). Le code de la formation est actuellement à jour pour la dernière version stable d’Angular qui est la version 13. Dans tous les cas, 95% de ce qui est enseigné dans ce cours sont des fondamentaux communs à toutes les versions d’Angular.

Bon développement,

Simon. »

Application de démonstration

Vous pouvez voir l’application que vous réaliserez pendant la formation en cliquant ci-dessous :

https://ng-pokemon-app-d761d.web.app

La Correction du code

Vous pouvez télécharger le code de la correction en cliquant sur le bouton ci-dessous :

Les Extraits de code

Vous pouvez copier-coller les extraits de code avant de suivre une étape vidéo du programme.

Chapitre 2 : Les Composants

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

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

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>

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>

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

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 { }

Chapitre 9 : Les Formulaires

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>

Chapitre 10 : Effectuer des requêtes HTTP standards

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>

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 {}

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.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();
    }
}