5. Chapitre 4 - Introduction à [Angular]
5.1. Sources
Ce chapitre prend appui sur la documentation officielle d’[Angular] : angular.dev.
5.2. D’AngularJS 1.x à [Angular]
Le document original (2014) utilisait AngularJS 1.x : un framework JavaScript (pas encore TypeScript) organisé autour de $scope, de contrôleurs, de directives (ng-repeat, ng-if…) et d’un mécanisme de détection des changements fondé sur la « boucle de digestion » ($digest) et la surveillance de variables ($watch).
À partir de 2016, ce framework a été intégralement réécrit sous le nom [Angular] (sans « JS »), en TypeScript, avec une architecture par composants. Les versions se sont ensuite succédé (2, 4… jusqu’à 22, utilisée dans ce document), avec deux évolutions majeures qu’il faut connaître avant d’aborder le code du client [RdvMedecins] :
- l’abandon des NgModules (@NgModule) au profit de composants standalone, autosuffisants ;
- l’introduction des signaux (signal()), un nouveau modèle de réactivité qui remplace $watch/$digest.
Ce chapitre présente ces notions une par une, avec de petits exemples indépendants de l’étude de cas - le chapitre suivant les retrouvera à l’œuvre dans le client [RdvMedecins] complet.
5.3. Le principe d’une application [Angular] : le « Single Page Application »
Une application [Angular] est une application monopage (« Single Page Application », SPA) : le serveur ne délivre au navigateur qu’une seule page HTML, contenant un unique fichier Javascript qui embarque toute la logique de l’application. Une fois cette page chargée, il n’y a plus jamais de rechargement de page complet : chaque interaction de l’utilisateur (changer de vue, envoyer un formulaire…) est traitée par le code Javascript déjà présent dans le navigateur, qui met à jour l’affichage et dialogue avec le serveur en arrière-plan via des requêtes HTTP asynchrones (le plus souvent en JSON). C’était déjà le principe du document original avec AngularJS 1.x - il n’a pas changé.
5.4. Les composants
Un composant est l’unité de base d’une application [Angular] : il associe une vue (un template HTML) à une classe TypeScript qui en détient l’état et le comportement - exactement le rôle que jouait, dans le document original, le couple « vue + contrôleur C » d’AngularJS 1.x, mais regroupé ici dans une seule et même entité.
Voici un tout petit composant, indépendant de l’étude de cas, qu’on pourrait créer avec ng generate component compteur :
```sql linenums="1"
// compteur.component.ts
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-compteur',
templateUrl: './compteur.component.html',
})
export class CompteurComponent {
readonly valeur = signal(0);
incrementer(): void {
this.valeur.update((v) => v + 1);
}
}
<!-- compteur.component.html -->
<p>Valeur : {{ valeur() }}</p>
<button (click)="incrementer()">+1</button>
```
- ligne 4 : [@Component]({...}) est un décorateur (même mécanisme qu’en [NestJS], cf. chapitre 2) qui déclare la classe qui suit comme un composant [Angular] ;
- ligne 5 : selector: 'app-compteur' : le nom de la balise HTML personnalisée (<app-compteur>) qui, utilisée dans le template d’un autre composant, insère celui-ci à cet endroit - le mécanisme de composition de vues, déjà présent (sous une forme différente, les directives) en AngularJS 1.x ;
- ligne 6 : templateUrl : le fichier HTML associé à ce composant (on peut aussi écrire le HTML directement dans le décorateur, avec la propriété template) ;
- ligne 9 : signal(0) crée une valeur réactive initialisée à 0 (voir section suivante) ;
- ligne 16 : {{ valeur() }} dans le template : les doubles accolades affichent une valeur calculée par la classe - une syntaxe identique à celle d’AngularJS 1.x, mais on notera les parenthèses : valeur est un signal, donc une fonction qu’il faut appeler pour lire sa valeur courante ;
- ligne 17 : (click)="incrementer()" : la parenthèse autour de click signifie « écoute l’événement DOM click » et appelle la méthode indiquée - l’équivalent exact de la directive ng-click d’AngularJS 1.x.
5.5. Les signaux : le nouveau modèle de réactivité
En AngularJS 1.x, [Angular] détectait qu’une donnée avait changé grâce à une boucle de vérification systématique (le $digest), qui reparcourait l’ensemble des expressions surveillées ($watch) à chaque interaction possible. Ce mécanisme, simple à utiliser, devenait coûteux à mesure qu’une application grossissait.
Un signal est un conteneur de valeur qui sait quand il change, et sait qui dépend de lui (un template, un autre calcul…) - [Angular] n’a donc plus besoin de tout revérifier : seules les parties de l’application qui dépendent réellement d’un signal modifié sont recalculées. Trois fonctions suffisent à comprendre l’essentiel :
Fonction | Rôle |
crée un signal ; on lit sa valeur en l’appelant comme une fonction : monSignal() | |
remplace la valeur du signal | |
calcule la nouvelle valeur à partir de l’ancienne (cf. incrementer() ci-dessus) | |
crée un signal calculé automatiquement à partir d’autres signaux, et recalculé seulement quand c’est nécessaire |
Deux variantes spécialisées de signal() servent à faire communiquer un composant avec son parent - on les retrouvera constamment dans le client [RdvMedecins] (chapitre suivant) :
- input() (et sa variante input.required()) déclare une entrée : une valeur fournie par le composant parent. C’est l’évolution moderne du décorateur @Input() historique, et l’équivalent, en esprit, d’un attribut de directive AngularJS 1.x ;
- output() déclare une sortie : un événement que le composant peut émettre vers son parent (méthode .emit(valeur)). C’est l’évolution moderne de @Output() / EventEmitter, et l’équivalent des événements personnalisés ($emit) d’AngularJS 1.x.
5.6. Les composants standalone
Une application AngularJS 1.x s’organisait en modules (angular.module(...)), déclarant contrôleurs, directives et services. [Angular] a d’abord repris cette idée sous une forme plus stricte (les NgModule, décorateur @NgModule), avant de proposer, depuis quelques versions, les composants standalone - devenus le mode de fonctionnement par défaut.
Un composant standalone déclare lui-même, dans son propre décorateur [@Component], tout ce dont il a besoin (les autres composants qu’il utilise dans son template, par exemple), au lieu de dépendre d’un module partagé :
C’est le style utilisé dans tout le client [RdvMedecins] (chapitre suivant) : plus aucun fichier *.module.ts n’y apparaît, une simplification appréciable par rapport à la fois à AngularJS 1.x et aux premières versions d’Angular.
5.7. Les services et l’injection de dépendances
Comme en [NestJS] (chapitre 2), un service [Angular] est une classe ordinaire, décorée par [@Injectable], que [Angular] sait créer et fournir automatiquement à tout composant qui en a besoin - la même idée d’injection de dépendances, dont AngularJS 1.x fut d’ailleurs l’un des tout premiers frameworks JavaScript à populariser le principe (ce n’est pas un hasard si [NestJS] s’en est directement inspiré, comme signalé au chapitre 2).
```python linenums="1"
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class MonService {
saluer(): string {
return 'Bonjour';
}
}
```
- ligne 3 : providedIn: 'root' indique qu’une seule instance de ce service est créée pour toute l’application (un singleton) - l’équivalent de ce qu’était un .service() ou un .factory() en AngularJS 1.x.
Pour obtenir ce service dans un composant, l’[Angular] moderne propose la fonction inject(), qui remplace l’injection par constructeur historique (les deux fonctionnent encore, mais inject() est devenu l’usage courant, et c’est celui du client [RdvMedecins]) :
5.8. Communiquer avec le serveur : [HttpClient]
Le module [HttpClient] ([@angular/common/http]) permet d’envoyer des requêtes HTTP - GET, POST… - vers un serveur, et de traiter sa réponse. C’est l’évolution directe du service $http d’AngularJS 1.x utilisé par le document original.
import { inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
const http = inject(HttpClient);
http.get<{ message: string }>('http://localhost:8080/bonjour').subscribe({
next: (reponse) => console.log(reponse.message),
error: (erreur) => console.error(erreur),
});
Une différence importante avec $http : les méthodes de [HttpClient] (get, post…) ne renvoient pas directement la réponse, ni une promesse (Promise) comme on pourrait s’y attendre, mais un Observable (bibliothèque RxJS) - un flux de valeurs dans le temps, auquel on s’abonne avec .subscribe({ next, error }) pour être notifié quand la réponse arrive (ou qu’une erreur survient). On peut également transformer un Observable avant de s’y abonner, avec des opérateurs comme map :
Le service [RdvService] du client [RdvMedecins] (chapitre suivant) utilise systématiquement cette combinaison .pipe(map(...)), pour un motif précis expliqué à ce moment-là.
5.9. La nouvelle syntaxe de contrôle de flux : [@if] et [@for]
AngularJS 1.x utilisait des directives structurelles (ng-if, ng-repeat) pour afficher un élément conditionnellement ou répéter un bloc de template pour chaque élément d’une liste. [Angular] a longtemps utilisé un équivalent très proche (*ngIf, *ngFor), avant d’introduire, dans ses versions récentes, une syntaxe intégrée au langage de template lui-même - celle utilisée dans tout le client [RdvMedecins] :
```html linenums="1"
@if (utilisateurConnecte()) {
<p>Bienvenue !</p>
} @else {
<p>Veuillez vous connecter.</p>
}
<ul>
@for (item of listeItems(); track item.id) {
<li>{{ item.nom }}</li>
}
</ul>
```
- ligne 8 : track item.[id] (obligatoire avec [@for]) indique à [Angular] comment reconnaître un élément déjà affiché lorsqu’il redessine la liste - un peu comme track by était déjà possible, en option, avec ng-repeat.
5.10. Récapitulatif : correspondances AngularJS 1.x -> [Angular]
AngularJS 1.x (2014) | [Angular] (2026) |
Composant (classe + template) | |
Signaux (signal(), computed()) | |
Composants standalone | |
input() / output() (signaux) | |
[@Injectable]({ providedIn: 'root' }) | |
inject() (ou toujours par constructeur) | |
[HttpClient] (Observables RxJS) | |
[@if] / [@for] | |
TypeScript |
Ces notions suffisent pour aborder le client [RdvMedecins] du chapitre suivant : chaque nouvelle utilisation y sera malgré tout réexpliquée, au fil du code, à l’endroit précis où elle apparaît.