À propos de ce cours
🎬 La leçon en vidéo : l'activité 2 corrigée pas à pas (20 min 01)
La vidéo ne s'affiche pas ? La regarder sur YouTube.
| Durée | Niveau | Outils | Livrable |
|---|---|---|---|
| 6 heures (10 parties) | Débutant, après les 11 premiers chapitres | Node.js 22.18+, Angular CLI 22, VS Code, Git | Dépôt Git avec un commit par partie et un README.md |
-
Objectifs
- Réaliser une application Angular complète, en partant d'un projet vide, en réinvestissant toutes les notions vues jusqu'ici
- Concevoir un modèle TypeScript typé (unions, interfaces, fonctions) et l'utiliser dans plusieurs composants
- Gérer l'état avec les signals (
signal,computed,linkedSignal,effect) - Utiliser toutes les liaisons: interpolation, propriété, classe, style, attribut, événement et two-way (
[(ngModel)],model()) - Gérer le clavier et les événements globaux, et faire communiquer des composants avec
input()etoutput() - Exploiter les variables de template (
#ref,@let,$index) et le contrôle de flux (@if,@for,@switch) - Travailler par étapes vérifiables, avec un commit Git à chaque point de contrôle
-
Prérequis
- Avoir suivi les chapitres : Présentation de TypeScript, Les types de variables, Le typage fort, La syntaxe de base, Les décorateurs, Le data binding, Le property binding, Le two-way data binding, Les signals, La gestion des événements, Les variables de template.
- Savoir créer un projet avec Angular CLI et le lancer avec
ng serve(voir « La syntaxe de base d'Angular »). - Ce TP prolonge le TP 5 Flutter : on y retrouve le compteur ASCII et le compteur numérique, avec leurs règles de couleur et leurs limites.
-
Présentation du projet
- Vous allez développer l'application Compteurs interactifs, composée de quatre zones :
- Compteur numérique: une valeur entre un minimum et un maximum, modifiée par un pas réglable, en vert si elle est positive et en rouge si elle est négative, avec une jauge, des raccourcis clavier et une fonction d'annulation ;
- Compteur ASCII: un caractère entre
A(65) etz(122), coloré selon sa nature (majuscule, minuscule, symbole), avec son code en hexadécimal et en binaire ; - Historique: la liste des dernières actions des deux compteurs, des statistiques, et un enregistrement dans le navigateur ;
- Mes compteurs du jour: une liste de petits compteurs à objectif (verres d'eau, pauses…), construite à partir d'un composant réutilisable.
- Vous allez développer l'application Compteurs interactifs, composée de quatre zones :


-
Organisation du TP
- Le TP est découpé en dix parties. Chacune indique sa durée, les notions mobilisées, les consignes, une aide, un point de contrôle à vérifier avant de continuer, et des questions.
- Répondez aux questions dans un fichier
README.md, à la racine du projet, au fur et à mesure. - À la fin de chaque partie, faites un commit:
git commit -m "Partie N : …". L'historique Git sert de preuve de votre progression. - Les aides donnent des squelettes de code, jamais la solution complète : c'est à vous de compléter.


-
Partie 0 : mise en place du projet
⏱ 20 min
Notions mobilisées : La syntaxe de base
- Créez le projet, puis lancez le serveur de développement :
npx @angular/cli@latest new tp2-compteur --defaults --ssr=false --style=css
cd tp2-compteur
npx ng serve
- Générez les six composants et créez le dossier du modèle :
npx ng generate component compteur-numerique
npx ng generate component compteur-ascii
npx ng generate component historique
npx ng generate component mes-compteurs
npx ng generate component mini-compteur
mkdir src/app/modeles
- Videz
app.html, puis placez-y un titre<h1>Compteurs interactifs</h1>et les balises<app-compteur-numerique />,<app-compteur-ascii />,<app-historique />et<app-mes-compteurs />. N'oubliez pas le tableauimportsdeApp. - Dans
styles.css, définissez les couleurs de l'application en variables CSS (--bleu,--vert,--rouge,--gris) et une classe.carte-tp(fond blanc, coins arrondis, ombre légère). Dansapp.css, disposez les quatre zones sur deux colonnes avec une grille CSS.
src/app/
├── app.ts / app.html / app.css
├── modeles/compteur.ts ← partie 1
├── compteur-numerique/ ← parties 2, 3, 4 et 6
├── compteur-ascii/ ← partie 5
├── historique/ ← partie 7
├── mes-compteurs/ ← partie 8
└── mini-compteur/ ← partie 8
✅ Point de contrôle
ng servefonctionne et la page affiche le titre et les quatre messages « … works! » sur deux colonnes.- La console du navigateur (F12) ne contient aucune erreur.
❓ Questions (à répondre dans le README.md)
- Quels trois fichiers la commande
ng generate componenta-t-elle créés pour chaque composant ? Quel est le sélecteur deMiniCompteur? - Que se passe-t-il si l'on oublie d'ajouter un composant au tableau
importsdeApp? Notez le code de l'erreur.
git add .
git commit -m "Partie 0 : création du projet et des composants"
-
Partie 1 : le modèle TypeScript
⏱ 30 min
Notions mobilisées : Présentation de TypeScript · Les types de variables · Le typage fort
- Toutes les données de l'application sont décrites dans un seul fichier,
src/app/modeles/compteur.ts. Il ne contient aucun code Angular : uniquement des types et des fonctions.- un type
NomCompteurqui n'accepte que'numerique'ou'ascii'; - un type
Operation:'plus','moins','reinitialiser','annuler'ou'saisie'; - un type
TypeCaractere:'majuscule','minuscule'ou'autre'; - une interface
Action:id(chaîne, en lecture seule),compteur,operation,avant,apres(nombres) etheure(Date) ; - une interface
Reglages:pas,minetmax; - deux constantes exportées
ASCII_MIN = 65etASCII_MAX = 122; - trois fonctions typées :
borner(valeur, min, max)qui ramène une valeur dans l'intervalle,typeCaractere(code), etcreerAction(compteur, operation, avant, apres)qui renvoie uneActiondatée avec un identifiant unique (crypto.randomUUID()).
- un type
- Aide: complétez ce squelette.
export type NomCompteur = /* à compléter */;
export type Operation = /* à compléter */;
export type TypeCaractere = /* à compléter */;
export interface Action {
readonly id: string;
/* à compléter */
}
export function borner(valeur: number, min: number, max: number): number {
// indice : Math.min et Math.max
}
export function typeCaractere(code: number): TypeCaractere {
// A–Z : 65 à 90 ; a–z : 97 à 122
}
- Vérifiez vos fonctions avec quelques
console.logprovisoires dans le constructeur deApp:
console.log(borner(15, -10, 10)); // 10
console.log(borner(-3, -10, 10)); // -3
console.log(typeCaractere(77), typeCaractere(100), typeCaractere(91)); // majuscule minuscule autre
console.log(creerAction('numerique', 'plus', 0, 1)); // { id: '…', compteur: 'numerique', …, heure: Date }
✅ Point de contrôle
- Les quatre lignes de test affichent exactement les résultats attendus, puis vous les supprimez.
creerAction('numerique', 'ajouter', 0, 1)est refusé par l'éditeur :'ajouter'n'est pas uneOperation.
❓ Questions (à répondre dans le README.md)
- Pourquoi utiliser des unions de chaînes (
'plus' | 'moins' | …) plutôt que le typestring? - Pourquoi
idest-ilreadonly? - Pourquoi un compteur (1, 2, 3…) ne suffirait-il pas comme identifiant, une fois l'historique enregistré dans le navigateur (partie 7) ?
git add .
git commit -m "Partie 1 : modèle TypeScript"
-
Partie 2 : le compteur numérique, signals et événements
⏱ 45 min
Notions mobilisées : La syntaxe de base · Le data binding · Les signals · La gestion des événements · Les décorateurs
- Dans
CompteurNumerique:- déclarez un signal
valeur(0) et un signalpas(1) ; - affichez la valeur entre deux boutons − 1 et + 1 (le texte des boutons utilise
pas()) ; les boutons appellentmoins()etplus(); - ajoutez un bouton Réinitialiser;
- toutes les modifications passent par une seule méthode privée
changer(operation, cible); - déclarez une sortie
action = output<Action>():changer()l'émet aveccreerAction(…).
- déclarez un signal
- Dans
App, déclarez un signalhistorique(tableau d'Action), écoutez la sortie avec(action)="noter($event)"et affichez dans l'en-tête le nombre d'actions enregistrées.
protected changer(operation: Operation, cible: number): void {
const avant = this.valeur();
// 1. modifier le signal 2. émettre l'action
}

✅ Point de contrôle
- Trois clics sur + 1 affichent 3 ; l'en-tête affiche « 3 action(s) enregistrée(s) ».
- Réinitialiser remet la valeur à 0 et ajoute une action.
{{ valeur }}, sans parenthèses, n'affiche pas la valeur. Angular le signale : NG8109: valeur is a function and should be invoked: valeur(). ❓ Questions (à répondre dans le README.md)
- Pourquoi la valeur doit-elle être un signal et non une propriété ordinaire (indice : Angular 22, sans
zone.js) ? - Quel est le type de
$eventdans(action)="noter($event)"? Et dans(click)="plus()", si on l'avait passé ? - Pourquoi faire passer toutes les modifications par une seule méthode
changer()?
git add .
git commit -m "Partie 2 : compteur numérique et historique minimal"
-
Partie 3 : limites, couleurs et jauge
⏱ 40 min
Notions mobilisées : Le property binding · Les signals · La syntaxe de base
- Le compteur reste entre
min(−10) etmax(10), deux nouveaux signals :- dans
changer(), utilisezborner(); si la valeur ne change pas, n'émettez aucune action ; - créez trois
computed:auMin,auMaxetposition(position de la valeur entre min et max, en pourcentage) ; - désactivez − au minimum et + au maximum avec
[disabled], ainsi que Réinitialiser quand la valeur vaut 0 ; - colorez la valeur avec
[class.positif](vert) et[class.negatif](rouge) ; - dessinez une jauge : une barre grise, un trait vertical à la position du zéro, et un curseur rond placé avec
[style.left.%]="position()", qui devient rouge à une limite ; - affichez avec
@ifle message « Limite maximale atteinte (10). » ou « Limite minimale atteinte (−10). » ; - pour l'accessibilité, donnez à l'élément de la valeur l'attribut
aria-valuenow([attr.aria-valuenow]), et aux boutons unaria-label.
- dans

✅ Point de contrôle
- Avec un pas de 5 (valeur fixe provisoire), deux clics depuis 3 donnent 8 puis 10, et non 13 : le bouton + 5 se grise, le curseur devient rouge et le message s'affiche.
- Un nouveau clic sur + désactivé n'ajoute rien à l'historique.
❓ Questions (à répondre dans le README.md)
- Quelle différence y a-t-il entre
[class.positif]="…"et[class]="…"? Lequel conserve la classevaleurdéjà présente ? - Pourquoi écrit-on
[style.left.%]plutôt que[style.left]="position() + '%'"? Les deux fonctionnent-ils ? positionest uncomputed: combien de fois est-il recalculé si l'on clique sur un bouton désactivé ?
git add .
git commit -m "Partie 3 : limites, couleurs et jauge"
-
Partie 4 : les réglages en two-way binding
⏱ 45 min
Notions mobilisées : Le two-way data binding · Les signals
- Ajoutez sous le compteur un
<fieldset>« Réglages » :- une liste Pas (1, 2, 5, 10) liée par
[(ngModel)]="pas", avec[ngValue]sur chaque option, générée par un@for; - deux champs numériques Min et Max liés par
[(ngModel)]aux signalsminetmax; - un
computedreglagesValides: le minimum doit être négatif et le maximum positif ; sinon, affichez un message d'erreur.
- une liste Pas (1, 2, 5, 10) liée par
- Problème : si la valeur vaut 8 et que l'on règle le maximum à 3, la valeur doit redescendre à 3. Transformez
valeurenlinkedSignal: il se recalcule quandminoumaxchange, en conservant la valeur précédente, bornée.
protected readonly valeur = linkedSignal<{ min: number; max: number }, number>({
source: () => ({ min: this.min(), max: this.max() }),
computation: (limites, precedent) => /* à compléter avec borner() */,
});

✅ Point de contrôle
- Changer le pas en 5 modifie aussitôt le texte des boutons (− 5, + 5).
- Avec une valeur de 4, régler le maximum à 3 ramène la valeur à 3 et affiche la limite atteinte.
- Saisir 4 comme minimum affiche « Le minimum doit être négatif et le maximum positif. ».
linkedSignal({ source, computation }) ne sait pas déduire le type de la valeur : TS2571: Object is of type 'unknown'. partout où valeur() est utilisée.FormsModule dans les imports du composant donne NG8002: Can't bind to 'ngModel' since it isn't a known property of 'select'.❓ Questions (à répondre dans le README.md)
- Pourquoi utiliser
[ngValue]et nonvaluesur les options de la liste Pas? Que contiendraitpas()sinon ? - Pourquoi un simple
computedne convient-il pas pourvaleur? Et uneffectqui corrigerait la valeur ? - Quel est le type de la valeur d'un champ
type="number"lié parngModel? Que vaut-elle si le champ est vidé ?
git add .
git commit -m "Partie 4 : réglages et linkedSignal"
-
Partie 5 : le compteur ASCII
⏱ 45 min
Notions mobilisées : Les signals · La syntaxe de base · Le property binding · Les variables de template
- Dans
CompteurAscii, un signalcode(65) et descomputed:caractere(String.fromCharCode),type(avectypeCaractere()),voyelle,hexaetbinaire(sur 8 chiffres).- deux boutons ◀ et ▶ (désactivés aux limites 65 et 122) ; au-delà, un message « Limite atteinte » ;
- le caractère en grand, coloré avec
[class]="type()": bleu pour une majuscule, vert pour une minuscule, orange pour un symbole ; les voyelles sont soulignées ([class.voyelle]) ; - une ligne « Code 101 · hexa 0x65 · binaire 01100101 », l'hexadécimal passant par le pipe
uppercase; - la nature du caractère avec
@switch(« Lettre majuscule », « Lettre minuscule », « Symbole (entre Z et a) »), suivie d'un badge « voyelle » ; - un champ
#saisied'un caractère : Entrée ((keydown.enter)) ou le bouton Aller appellentchercher(saisie), qui va directement au caractère tapé, vide le champ et lui redonne le focus ; un caractère hors limites affiche un message ; - chaque changement émet une
Action(compteur: 'ascii') : dansApp, écoutez-la comme pour le compteur numérique.

✅ Point de contrôle
- Taper
epuis Entrée affiche un e vert souligné, « Code 101 · hexa 0x65 · binaire 01100101 » et « Lettre minuscule · voyelle ». - De
Z, le bouton ▶ mène à[, en orange : « Symbole (entre Z et a) ». - Taper
5affiche « « 5 » n'est pas entre A et z. ».
- Variante individuelle (comme dans le TP Flutter) : ajoutez une règle d'affichage de votre choix, par exemple les consonnes en gras, les lettres de votre prénom encadrées, ou une couleur différente pour chaque groupe de symboles. Décrivez-la dans le README.
❓ Questions (à répondre dans le README.md)
- Quels caractères se trouvent entre
Z(90) eta(97) ? - Pourquoi transmettre l'élément
#saisieà la méthode plutôt que$event? - Pourquoi utiliser
@switchplutôt que trois@if?
git add .
git commit -m "Partie 5 : compteur ASCII"
-
Partie 6 : raccourcis clavier et annulation
⏱ 30 min
Notions mobilisées : La gestion des événements · Les signals
- Le compteur numérique se pilote aussi au clavier, sans clic préalable. Dans son décorateur, écoutez
'(document:keydown)'avec la propriétéhostet une méthodeclavier(e: KeyboardEvent):
| Touche | Action |
|---|---|
↑ ou + |
augmenter du pas |
↓ ou - |
diminuer du pas |
0 |
réinitialiser |
| Ctrl + Z | annuler la dernière modification |
- ignorez les touches tapées dans un champ (
INPUT,SELECT,TEXTAREA) : on doit pouvoir saisir « -5 » dans Min sans modifier le compteur ; - appelez
preventDefault()pour les flèches (sinon la page défile) et pour Ctrl + Z ; - annulation: un signal privé
precedentes(tableau des 20 dernières valeurs) est complété parchanger();annuler()revient à la dernière valeur et émet une action'annuler'; un bouton Annuler (n) indique le nombre d'annulations possibles ; - dans
CompteurAscii, utilisez cette fois les filtres de touches:'(document:keydown.arrowleft)'et'(document:keydown.arrowright)'.
✅ Point de contrôle
- Après un clic dans le vide de la page : ↑ ↑ ↑ donne 3, Ctrl + Z donne 2,
0remet à zéro. - Dans le champ Max, la flèche ↑ modifie le champ, pas le compteur.
- ← et → changent le caractère ASCII, même quand le compteur numérique a été utilisé juste avant.
❓ Questions (à répondre dans le README.md)
- Pourquoi l'écouteur est-il placé sur
documentet non sur le composant ? - Pourquoi tester
e.keydans une méthode pour le compteur numérique, alors qu'un filtre suffit pour le compteur ASCII ? - Pourquoi
precedentesest-il limité à 20 valeurs ?
git add .
git commit -m "Partie 6 : clavier et annulation"
-
Partie 7 : l'historique
⏱ 45 min
Notions mobilisées : Les décorateurs · La syntaxe de base · Les signals · Les variables de template
- Le composant
Historiquereçoit les actions deApp:actions = input.required<Action[]>(), et prévient le parent avecvider = output<void>(). DansApp:<app-historique [actions]="historique()" (vider)="historique.set([])" />.computed:recentes(les 8 dernières, de la plus récente à la plus ancienne),numeriques,plusHauteetplusBasse(valeurs extrêmes atteintes) ;- cinq statistiques : nombre total d'actions (avec
@let total = actions().length;), numériques, ASCII, plus haut, plus bas ; - la liste avec
@for (a of recentes(); track a.id; let premiere = $first, i = $index): un numéro (total - i), le libellé de l'opération, « avant → après » (des caractères pour l'ASCII), l'heure avec le pipedate: 'HH:mm:ss'; la première ligne est mise en valeur ;@emptyaffiche « Aucune action pour l'instant » ; - sous la liste : « … et N action(s) plus ancienne(s) » s'il y en a plus de 8 ;
- enregistrement: dans le constructeur de
App, uneffectenregistre les 50 dernières actions danslocalStorage(dans untry … catch) ; au démarrage,historiqueest initialisé en relisant ces données, et chaqueheureest reconvertie enDate.
- Pour afficher les dates en français, ajoutez dans
app.config.ts:registerLocaleData(localeFr)et le fournisseur{ provide: LOCALE_ID, useValue: 'fr' }.

✅ Point de contrôle
- Chaque action des deux compteurs apparaît en haut de la liste, avec son heure.
- Après un rechargement de la page (F5), l'historique et les statistiques sont toujours là.
- Vider affiche « Aucune action pour l'instant » et l'en-tête revient à 0.
❓ Questions (à répondre dans le README.md)
- Pourquoi faut-il recréer les objets
DateaprèsJSON.parse? Que se passerait-il avec le pipedatesinon ? - Pourquoi utiliser un
effectpour l'enregistrement, et non uncomputed? - Pourquoi
Historiquene modifie-t-il pas lui-même la liste, mais émetvider?
git add .
git commit -m "Partie 7 : historique et enregistrement"
-
Partie 8 : un composant réutilisable
⏱ 45 min
Notions mobilisées : Les décorateurs · Le two-way data binding · La gestion des événements · Les variables de template
MiniCompteurest un petit compteur à objectif, conçu pour être réutilisé :- entrées :
libelle(obligatoire),icone(« • » par défaut),max(10 par défaut, converti avecnumberAttribute) ; valeur = model(0): une valeur que le parent fournit et que l'enfant modifie ;- sorties :
limiteAtteinte(émet le libellé quand la valeur atteintmax) etsupprimer; - affichage : l'icône, le libellé, une barre de progression (
[style.width.%]), les boutons − et + (bornés à 0 etmax), « 3 / 8 » et un bouton × ; la ligne passe en vert quand l'objectif est atteint.
- entrées :
MesCompteursaffiche une liste de suivis (signal de tableau{ id, libelle, icone, max, valeur }, avec trois suivis de départ) :- un
@forde<app-mini-compteur>, avec[valeur]="s.valeur"et(valeurChange)="changer(s.id, $event)"; - dans l'en-tête, le total et l'objectif global (« 14 / 29 ») ; en bas, « N objectif(s) atteint(s) sur M » et le message « Bravo : objectif « … » atteint ! » ;
- un formulaire d'ajout avec deux variables de référence,
#nomet#max: Entrée dans le nom ou le bouton Ajouter appellentajouter(nom, max), qui vide le champ et lui redonne le focus.
- un

✅ Point de contrôle
- Cinq clics sur + de « Verres d'eau » (3 / 8) mènent à 8 / 8 : la ligne devient verte, le + se grise, le message « Bravo » s'affiche et le total passe de 4 à 9.
- L'ajout de « Pages lues » (objectif 12) crée une ligne ⭐ 0 / 12 ; × supprime une ligne et met le total à jour.
[(valeur)]="s.valeur" dans le @for compile, mais modifie l'objet sur place: le mini-compteur affiche 5 / 8 alors que le total reste à 4 / 17, car le signal suivis n'est pas prévenu. Il faut créer un nouveau tableau dans changer(). ❓ Questions (à répondre dans le README.md)
- Quelles deux liaisons
model()crée-t-il pour le parent ? Écrivez l'équivalent « long » de[(valeur)]. - Pourquoi
MiniCompteurne connaît-il pas la liste des suivis ? Quel avantage pour sa réutilisation ? - À quoi sert
track s.idquand on supprime une ligne au milieu de la liste ?
git add .
git commit -m "Partie 8 : composant réutilisable"
-
Partie 9 : variantes et finitions
⏱ 15 min
- Choisissez deux variantes (chaque binôme annonce ses choix au formateur, pour varier les projets) :
- un champ Pas personnalisé (de 1 à 50) en plus de la liste ;
- un thème sombre, mémorisé dans
localStorageavec uneffect; - une animation (classe CSS temporaire) à chaque changement de valeur ;
- un export de l'historique au format CSV, dans une zone de texte à copier ;
- un objectif modifiable directement dans chaque mini-compteur ;
- une variante libre, validée par le formateur.
- Relisez votre code : noms explicites, membres
protectedouprivatequand ils ne servent pas au parent, aucunconsole.logoublié, aucune erreur dans la console.
git add .
git commit -m "Partie 9 : variantes et finitions"
-
Grille d'évaluation (sur 20)
| Critère | Points |
|---|---|
| Partie 0–1 : projet, structure, modèle TypeScript typé (unions, interfaces, fonctions) | 2 |
| Partie 2–3 : compteur numérique, signals, limites, liaisons de propriété, de classe et de style | 3 |
Partie 4 : réglages en two-way binding, linkedSignal, validation |
2 |
Partie 5 : compteur ASCII, computed, @switch, pipe, #saisie, variante individuelle |
3 |
Partie 6 : raccourcis clavier, host, annulation |
2 |
Partie 7 : historique, input / output, @for, @let, persistance |
3 |
Partie 8 : composant réutilisable avec model(), liste et formulaire d'ajout |
2 |
| Qualité : lisibilité, typage strict, aucune erreur ni avertissement, un commit par partie | 1 |
| README : réponses aux questions et description des variantes | 2 |
| Total | 20 |
-
Livrables
- Le lien du dépôt Git (GitHub, GitLab…), avec au moins dix commits nommés « Partie N : … ».
- Le fichier
README.md: vos noms, les réponses aux questions de chaque partie, les deux variantes choisies et une capture d'écran de l'application terminée. - Le projet doit se lancer avec
npm installpuisnpx ng serve, sans erreur ni avertissement.
-
Erreurs fréquentes
| Message ou symptôme | Cause | Solution |
|---|---|---|
TS2571: Object is of type 'unknown'. sur valeur() |
linkedSignal (forme longue) sans types |
linkedSignal<{ min: number; max: number }, number>({ … }) |
NG8002: Can't bind to 'ngModel' since it isn't a known property of 'select'. |
FormsModule absent des imports du composant |
L'ajouter au tableau imports |
NG5002: @for loop must have a "track" expression |
@for sans track |
track p, track a.id… |
TS2339: Property 'Math' does not exist on type 'CompteurNumerique'. |
Math utilisé dans le template |
Faire le calcul dans un computed |
NG8109: valeur is a function and should be invoked: valeur() |
Signal sans parenthèses dans le template | {{ valeur() }} |
| Le total des mini-compteurs ne change pas | [(valeur)]="s.valeur": objet modifié sur place |
(valeurChange) et un nouveau tableau avec update() |
| La page défile quand on utilise les flèches | preventDefault() oublié |
L'appeler dans clavier() |
| Le compteur change pendant la saisie dans Min | Les touches tapées dans un champ ne sont pas ignorées | Tester e.target.tagName |
| Après F5, l'heure n'est plus une date ou l'historique est vide | heure restée en chaîne, ou erreur non gérée dans lire() |
new Date(a.heure) et try … catch |
pas() contient « 5 » au lieu de 5 |
value au lieu de [ngValue] sur les options |
[ngValue]="p" |
-
Pour aller plus loin
- Déplacer l'historique dans un service injecté avec
inject(): c'est l'objet du chapitre « Les services et l'injection de dépendances ». - Remplacer
HistoriqueetMesCompteurspar deux pages accessibles depuis un menu : ce sera l'objet du chapitre sur le routage. - Écrire un test unitaire pour
borner()ettypeCaractere()avecnpx ng test.
- Déplacer l'historique dans un service injecté avec
-
Bilan
- Un modèle TypeScript partagé rend toutes les données explicites et vérifiées.
- Les signals portent l'état ;
computeden déduit tout le reste ;linkedSignaleteffecttraitent les cas particuliers. - Les liaisons relient la classe et la vue :
{{ }},[ ],( ),[( )]. - Les composants communiquent par
input(),output()etmodel(), sans se connaître. - Les variables de template et le contrôle de flux gardent les templates courts et lisibles.
Un bon compteur n'est pas celui qui compte le plus vite, mais celui dont chaque valeur affichée découle, sans surprise, d'une donnée claire : c'est tout l'esprit d'Angular moderne.