1. Références

2. Mise en place

Les étapes nécessaires pour bien démarrer le travail sont les suivantes.

2.1. Récupération des sources

Pour disposer des sources, utiliser git :

git clone https://github.com/formations/angular-foundations

L’étape de départ est la branche master.

2.2. Outillage

Le workshop est un projet basé sur Angular. Pour travailler, les outils suivants sont requises :

npm

Il faut tout d’abord installer npm. Puis, installer les dépendances npm en local :

npm install

Il est nécessaire d’installer la ligne de commande (CLI) Angular:

npm install -g @angular/cli
Par rapport à AngularJS, Angular fournit tout le reste de l’outillage nécessaire du projet (transpilation TypeScript en JavaScript, tests, etc.).

2.3. Création du projet

Cette étape est à visée informative uniquement et peut être passée. En effet, le projet disponible a déjà pris soin de réaliser celle-ci.

2.4. Structure de départ

La structure initiale du projet cloné est la suivante :

Structure initiale du projet

Les principaux dossiers/fichiers sont les suivants :

Dossier / fichier Description

e2e

Tests de bout-en-bout

node_modules

Modules node.js

src/app/about

Ecran A propos

src/app/comic-detail

Ecran de détail de comic

src/app/home

Ecran d’accueil

src/app/nav-bar

Composant de barre de navigation

src/app/tab-bar

Composant de barre d’onglets

app.component.css

Feuille de style du composant racine

app.component.html

Fragment HTML du composant racine

app.component.spec.ts

Tests relatifs au composant racine

app.component.ts

Code relatif au composant racine

app.module.ts

Module principal de l’application

data.ts

Données "bouchon"

model.ts

Modèle de classes TypeScript

assets

Resources statiques, y compris les images

environments

Liste des environnements disponibles. Par défaut, dev et prod.

3. Premiers pas

3.1. Lancement de l’application

Dans le répertoire racine du projet, lancer l’application :

ng serve --open
L’option --open permet d’ouvrir automatiquement une fenêtre de navigateur. Si elle n’est pas utilisée, naviguer à http://localhost:4200/.

Le résultat qui doit s’afficher est le suivant :

Etat initial de l’application

3.2. Affichage de données

Objectif
Le but de cette section est d’afficher les attributs du composant racine dans la page.

Voici le code source du composant racine :

src/app/app.component.ts
import { Component } from '@angular/core';            (1)

@Component({
  selector: 'app-root',                               (2)
  templateUrl: './app.component.html',                (3)
  styleUrls: ['./app.component.css']                  (4)
})
export class AppComponent {                           (5)
  title = 'app';                                      (6)
  id = 21464;                                         (6)
  title = 'Powers (2000)';                            (6)
  issueNumber = 18;                                   (6)
  thumbnail = 'image/image_not_available.jpg';        (6)
  price = 3.45;                                       (6)
  description = 'Walker and Pilgrim investigate...';  (6)
 }
1 TypeScript nécessite l’utilisation d’imports. Dans le cas contraire, le type ne peut pas être utilisé.
2 Sélecteur HTML qui doit être ajouté dans la page pour afficher le fragment HTML
3 Fragment HTML associée au composant. C’est le contenu de ce fichier HTML qui sera ajouté à la page web.
4 Feuille de style associée au composant
5 Le mot-clé export est nécessaire pour que le composant soit utilisé
6 Attributs disponibles dans le composant HTML

En utilisant la syntaxe {{ }}, afficher les attributs dans la page.

src/app/app.component.html
<div class="container">
  <h1>Comics Library</h1>
  <div class="row">
    <div class="col-xs-12">
      <div class="media">
        <img class="mr-3" alt="Comic cover">                        (1)
        <div class="media-body">
          <h2 class="media-heading">                                (2)
            <span class="badge badge-danger">#                      (3)
            <span class="badge badge-pill badge-secondary">$</span> (4)
          </h2>
          <p></p>                                                   (5)
        </div>
      </div>
    </div>
  </div>
</div>
1 Ajouter l’attribut src pour afficher l’image du composant
Les images se trouvent dans le répertoire assets
2 Afficher l’attribut title
3 Afficher l’attribut issueNumber
4 Afficher l’attribut price
5 Afficher l’attribut description

Le résultat attendu est le suivant :

Etat initial de l’application

3.3. Gestion de composant

Objectif
Le but de cette section est de créer un composant personnalisé.
Création de composants :

La création de composants se fait via la ligne de commande. Pour créer un composant nommé comicsList, utiliser la commande suivante :

ng generate component comicsList

Après l’exécution de la commande, un nouveau répertoire src/app/comics-list a été généré qui contient les fichiers suivants :

Fichier Description

comics-list.component.css

Feuille de style du composant liste

comics-list.component.html

Fragment HTML du composant liste

comics-list.component.spec.ts

Tests relatifs au composant liste

comics-list.component.ts

Code relatif au composant liste

Personnalisation :

Par défaut :

  • le nom de la classe est ComicslistComponent dans le code

  • et le sélecteur <app-comics-list>

Il est conseillé d’utiliser un préfixe quel qu’il soit, afin d’éviter que des balises de bibliothèques différentes aient le même nom. Le préfixe par défaut est app Il est configuré dans le fichier angular.json.
Utilisation du composant :

Déplacer le code du composant depuis le composant racine app.component vers le nouveau composant créé. Puis modifier le composant racine ainsi :

src/app/app.component.html
<div class="container">
  <h1>Comics Library</h1>
  <app-comics-list></app-comics-list> (1)
</div>
1 Ajout du sélecteur, qui va afficher le fragment HTML sur la page

L’affichage doit être exactement similaire à l’affichage de l’étape précédente.

3.4. Utilisation de classe et de données

  1. Remplacer les différents attributs de la classe ComicsListComponent par un unique attribut de type SimpleComic :

    src/app/comics-list/comics-list.component.ts
    export class ComicsListComponent {
    
      comic: SimpleComic = {                               (1)
        id: 21464,
        title: 'Powers (2000)',
        issueNumber: 18,
        thumbnail: 'image/image_not_available.jpg',
        price: 3.45,
        description: 'Walker and Pilgrim investigate...'
      }
    }
    1 Remplacement des différent attributs par un unique attribut

    La classe SimpleComic est déclarée dans le fichier model.ts. Ne pas oublier de l’importer.

    Modifier le composant HTML en conséquence.

  2. Remplacer la définition de la donnée dans le composant par la constante COMIC :

    src/app/comics-list/comics-list.component.ts
    export class ComicsListComponent {
    
      comic: SimpleComic = COMIC; (1)
    }
    1 Utilisation de la constante existante

    La constante COMIC est déclarée dans le fichier data.ts. Ne pas oublier de l’importer.

Au cours des deux manipulations précédentes, l’affichage doit être similaire à l’étape précédente.

4. Directives

4.1. Affichage de collection d’éléments

Objectif
Le but de cette section est d’afficher non plus un unique comic mais une collection de ceux-ci.
  1. Dans la classe ComicsListComponent :

    • Modifier l’attribut comic de type SimpleComic en attribut comics de type Comic[]

      src/app/comics-list/comics-list.component.ts
      export class ComicsListComponent {
      
        comic: SimpleComic = COMIC; (1)
      }
      1 Modifier cette ligne
    • Affecter la constante COMICS du fichier data.ts à ce dernier. Ne pas oublier l’import !

  2. Modifier le fragment HTML pour boucler sur les éléments de la collection en utilisant la directive ngForOf

    src/app/comics-list/comics-list.component.html
    <ul class="list-unstyled">
        <li class="media mt-5">                                              (1)
            <img class="mr-3" alt="Comic cover">                             (2)
            <div class="media-body">
                <h2 class="media-heading">                                   (3)
                    <span class="badge badge-danger">#</span>                (4)
                    <span class="badge badge-pill badge-secondary">$</span>  (5)
                </h2>
                <p></p>                                                      (6)
            </div>
        </li>
    </ul>
    1 Boucler sur les comics de la collection
    2 Utiliser l’attribut src pour afficher l’image
    3 Afficher le titre
    4 Afficher le numéro
    5 Afficher le prix
    6 Afficher la description
    La structure des classes SimpleComic et Comic est légèrement différente. Par exemple, le chemin de l’image n’est plus stocké dans un attribut de type simple string, mais dans un type qui comporte deux attributs, path et extension. Adapter le binding pour refléter la nouvelle structure.

Le résultat attendu est le suivant :

Affichage d’une collection d’éléments

4.2. Affichage conditionnel d’éléments

Objectif
Le but de cette section est d’afficher un élément si une condition est vraie.

Dans l’état actuel, le prix est affiché dans tous les cas. Or, il peut s’avérer que le prix ne soit pas renseigné (price: 0). Dans ce cas, il ne fait pas de sens de l’afficher.

Dans le fragment HTML, utiliser la directive ngIf pour afficher le prix uniquement si celui est supérieur strictement à 0.

Le résultat attendu est le suivant :

Affichage conditionnel du prix

4.3. Classe conditionnelle

Objectif
Le but de cette section est d’appliquer une classe CSS à un élément si une condition est vraie.
Préparation :

Le composant de barre d’onglets a déjà été créé et est prêt à être utilisé. L’ajouter au fragment HTML racine :

app.component.html
<div class="container">
    <h1>Comics Library</h1>
    <app-tab-bar></app-tab-bar>         (1)
    <app-comics-list></app-comics-list>
</div>
1 Ajouter le composant de barre d’onglets

Le résultat attendu est le suivant :

Affichage initial des onglets
Gestion de l’état "sélectionné" :

Modifier la classe TabBarComponent ainsi :

  1. Créer un attribut selectedIndex de type number initialisé à 0

  2. Créer une fonction isSelected(index: number): boolean qui retourne :

    • true si index est égal à selectedIndex

    • false sinon

Mise en exergue de l’onglet sélectionné :

Utiliser la directive ngClass pour faire en sorte que la classe CSS active soit affecté à l’onglet si et seulement s’il est sélectionné.

Le résultat attendu est le suivant :

Highlight de l’onglet sélectionné

5. Event Binding

Objectif
Le but de cette section est d’associer l’appel d’une fonction au clic sur un élément.
  1. Dans la classe TabBarComponent, créer une fonction select(index: number) qui modifie l’attribut selectedIndex avec l’index passé en paramètre

  2. Modifier le fragment HTML pour qu’un clic sur l’onglet appelle la méthode précédente

Le résultat attendu est le suivant :

Sélection de l’onglet

6. Communication inter-composants

Objectif
Le but de cette section est d’envoyer des informations depuis le composant onglet vers le composant liste.

A cette étape, le composant onglet connaît l’onglet sélectionné, et doit en faire part au composant liste lorsqu’il change. Plusieurs méthodes sont disponibles pour l’interaction de 2 composants. Comme l’onglet et la liste n’ont pas de relation parent-enfant, un service intermédiaire est requis.

Voici le diagramme qui va être implémenté :

service communication sequence diagram

6.1. Génération du service

A l’instar des composants, les services sont créés à l’aide de la ligne de commande.

Créer un service tab :

ng generate service tab

Après l’exécution de la commande, les fichiers suivants ont été créés :

Fichier Description

tab.service.ts

Tests relatifs au service

tab.service.ts

Code du service

En tant qu’intermédiaire, le service doit permettre de publier et de souscrire à des évènements. Angular intègre nativement le framework RxJs.

Il est toujours nécessaire d’utiliser import pour utiliser les objets d’un autre espace de nommage.

Le service doit adopter la conception suivante :

tab service class diagram
  • l’attribut titleChangedSource est un nouvel objet de type Subject<string>

  • l’attribut titleChanged$ est le même objet que l’objet précédent, mais trans-typé en Observable<String>

    Par convention, les attributs de type Observable sont suffixés par $.
  • la fonction changeTitle(title: string) appelle la fonction next() de titleChangedSource avec comme paramètre title

6.2. Injection du service

Avec Angular, les services ne sont pas instanciés manuellement mais par le framework (comme les composants). Il est donc nécessaire d’injecter l’instance existante dans les composants.

Pour ce faire, modifier le constructeur des composants TabComponent et ComicsListComponent en passant le type demandé :

export class XComponent {

  constructor(tabService: TabService) { }
}

6.3. Enregistrement du callback

Le composant ComicsListComponent joue le rôle d' Observer: il doit enregistrer à l’initialisation une fonction de callback qui sera appelée à chaque fois que le titre sera modifié. L’initialisation se fait via l’implémentation de l’interface OnInit, et sa fonction associée ngOnInit().

Pour s’abonner aux changements, utiliser la fonction subscribe() avec comme paramètre une fonction de callback dont l’algorithme est le suivant :

  • SI le paramètre est null

    • ALORS retourner la liste complète

  • SINON

    • retourner uniquement la liste de comics dont le titre contient le paramètre

Consulter la documentation de la méthode filter()

6.4. Publication d’éléments

Modifier l’implémentation de la fonction TabComponent.select() pour qu’elle appelle la fonction TabService.changeTitle(title: string) avec le paramètre correct.

Le résultat attendu est le suivant :

Filtrage de la collection en fonction de l’onglet

7. Pipes

Objectif
Le but de cette section est d’implémenter un pipe Angular.

Jusqu’à présent, les comics ne sont pas ordonnés. Il serait souhaitable qu’ils le soient, d’abord par titre, puis par numéro dans le titre.

Une solution pour aboutir à cela est d’appeler la fonction sort() (avec comme paramètre un comparateur) à chaque fois que l’on retourne la collection de comics.

Fonction comparateur

La fonction comparateur passée en argument de la fonction sort() accepte deux arguments - les éléments à comparer et retourne :

  • un entier positif si le premier élément est "supérieur" au second

  • un entier négatif s’il est "inférieur"

  • 0 si les deux éléments sont égaux

A titre d’exemple, voici le code source de la fonction comparateur qui compare deux comics, d’abord en fonction de leur attribut title, puis de leur attribut issueNumber en cas d’égalité.

private byTitle = (comic1, comic2) => {
  let byTitle = comic1.title.localeCompare(comic2.title);
  if (byTitle == 0) {
    return comic1.issueNumber - comic2.issueNumber;
  }
  return byTitle;
}

Le problème d’appeler la fonction sort() explicitement est qu’il est possible d’oublier un tel appel. La solution proposée par Angular est de configurer le filtre sur le fragment HTML via un pipe.

7.1. Création de pipe

Comme précédemment, la création de pipe se fait via la ligne de commande.

Pour créer un pipe nommé sortByTitle, utiliser la commande suivante :

ng generate pipe sortByTitle

Le résultat est le suivant :

src/app/sort-by-title.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({                                                  (1)
  name: 'sortByTitle'                                    (2)
})
export class SortByTitlePipe implements PipeTransform {  (3)

  transform(value: any, args?: any): any {               (4)
    return null;
  }
}
1 Identifie la classe comme un pipe
2 Nom du pipe à utiliser dans le fragment HTML
3 L’interface est optionelle, mais conseillée
4 La fonction transform() est essentielle au pipe. Le paramètre value représente l’entrée, et la valeur retournée par la fonction la sortie. Le paramètre args? permet d’ajouter des paramètres au pipe.

Adapter la signature de la fonction pour trier la collection de comics comme ci-dessus. Puis, réutiliser la méthode byTitle() à bon escient.

7.2. Utilisation de pipe

L’utilisation de pipe dans le fragment HTML se fait via l’utilisation du symbole | puis le nom du pipe.

src/app/comicslist.component.html
<li class="media mt-5" *ngFor="let comic of comics | sortByTitle">

Le résultat attendu est le suivant :

Tri de la collection

8. Routage

Objectif
Le but de cette section est d’associer des éléments cliquables à des composants, de telle sorte qu’un clic sur un tel élément affiche le composant associé.

8.1. Création de module

En plus des composants et des services, les modules sont créés à la ligne de commande. Créer un module routing :

ng generate module routing  --flat --module=app
Signification des options
--flat :

Crée le nouveau module à la racine du répertoire app plutôt que dans un sous-module

--module :

Importe le nouveau module dans le module principal de l’application

L’exécution de la commande crée les fichiers suivants :

Fichier Description

routing.module.specs.ts

Tests relatifs au module

routing.module.ts

Code du module

Dès lors que l’application comporte plus d’un module, la génération d’un nouveau fichier via ng generate nécessite d’indiquer le module auquel celui-ci sera rattaché

8.2. Implémentation des routes

Implémenter les routes suivantes :

Chemin Composant

HomeComponent

comics

ComicsListComponent

about

AboutComponent

Les composants HomeComponent et AboutComponent sont respectivement disponibles dans les répertoires home et about

Utiliser les routes définies précédemment dans le fragment src/app/nav-bar/nav-bar.component.html à l’aide de la directive routerLink.

8.3. Affichage des fragments

Au niveau de l’affichage du composant app.component.html :

  1. Supprimer la balise <h1>

  2. Ajouter le fragment <app-nav-bar> à la racine

  3. Déplacer le fragment <app-tab-bar> au niveau du composant comics-list.component.html

  4. Remplacer le fragment <app-comics-list> par le fragment dédié au routage

Le résultat attendu est le suivant :

Utilisation de la barre de navigation

9. Module Client HTTP

Objectif
Le but de cette section est d’utiliser le module client HTTP pour récupérer les données d’un serveur en lieu et place des donnée statiques.

9.1. Refactoring

Nettoyage du code :

D’un point de vue conceptuel, un composant ne doit pas gérer lui-même de données, mais utiliser un service pour cela.

  1. Créer un service comic

    ng generate service comic
  2. Dans le nouveau service ComicService, créer une fonction getComics() qui retourne la constante COMICS

  3. Dans le composant ComicsListComponent, remplacer l’utilisation de cette dernière par le service

Changement de type :

Le module client HTTP utilise des types de retour asynchrones - Observable. Il est nécessaire d’adapter la chaîne de traitement dès à présent.

  1. Dans la classe ComicService, remplacer le type de retour de la fonction getComics() de Comics[] à Observable<Comic[]>

    Utiliser la fonction of
  2. Adapter la classe ComicListComponent :

    • Modifier le type de l’attribut comics en Observable<Comic[]>

    • Modifier son nom en comics$

    • Adapter l’implémentation de la fonction ngInit()

  3. Pour disposer d’un tableau dans le fragment HTML, utiliser le pipe async

9.2. Création et configuration de compte

  1. La première étape consiste à créer un compte développeur sur developer.marvel.com.

  2. Puis, récupérer la clé d’API publique.

  3. Ne pas oublier de mettre à jour la liste des site habilités avec localhost.

Marvel Comics developer account

9.3. Utilisation du module

  1. Dans la classe AppModule, importer le module HttpClientModule depuis @angular/common/http

  2. Abandonner la notion d’onglets. Dans le fragment comics-list.component.html, supprimer <app-tab-bar>

  3. Abandonner le filtre. Supprimer également l’utilisation du pipe sortByTitle (mais pas celle du pipe async !)

  4. Dans le fichier environment.ts, créer deux entrées supplémentaires :

    Clé Valeur

    baseUrl

    "https://gateway.marvel.com:443"

    apiKey

    Clé d’API disponible sur marvel.com

  5. Dans le service ComicService, injecter le service HttpClient. Utiliser ce dernier en conjonction avec les paramètres d’environnement pour exécuter la requête qui retourne la liste des comics.

  6. Dans le composant ComicsListComponent, injecter le service ComicService et supprimer l’injection du TabService

Le résultat attendu doit être semblable à la copie d’écran suivante :

Affichage de la liste des comics depuis le serveur

Une fois la liste affichée, modifier le code pour qu’un clic sur un comics affiche son détail.

Utiliser le composant comic-detail.component et les routes avec paramètres

Voici un exemple de résultat attendu :

Affichage d’un comic depuis le serveur