À propos de ce cours
🎬 La correction du TP en vidéo : la TodoList construite partie par partie (17 min 11)
La vidéo ne s'affiche pas ? La regarder sur YouTube.
| Durée | Niveau | Outils | Livrable |
|---|---|---|---|
| 6 heures (10 parties) | Débutant, après le chapitre « Interaction entre composants » | Node.js 22.18+, Angular CLI 22, VS Code, Git | Dépôt Git avec un commit par partie et un README.md |
-
Objectifs
- Construire une application Angular complète, découpée en sept composants qui ont chacun un rôle unique
- Appliquer la règle d'or de la communication : les données descendent par
input(), les événements remontent paroutput(), un seul composant possède les données - Relayer des événements sur plusieurs niveaux, et modifier un tableau d'objets de façon immuable
- Utiliser
model()pour une liaison bidirectionnelle entre composants - Commander un enfant avec
viewChild(): appeler ses méthodes, accéder au DOM (focus(),<dialog>) - Réinvestir les signals (
computed,linkedSignal,effect), les événements clavier et les variables de template - Travailler par étapes vérifiables, avec un commit Git à chaque point de contrôle
-
Prérequis
- Avoir suivi les leçons du chapitre « Interaction entre composants » : Les composants, La communication entre composants, Transmettre des données avec input(), Émettre des événements avec output(), Accéder à un composant enfant avec viewChild.
- Avoir réalisé le TP 2 : compteur interactif : la création de projet, les signals, les liaisons et les événements clavier sont supposés acquis.
-
Présentation du projet
- Vous allez développer Mes tâches, une liste de tâches (TodoList) complète :
- ajouter une tâche avec un titre, une priorité (haute, normale, basse) et une échéance facultative ;
- cocher, modifier (double-clic) et supprimer une tâche ; une tâche dont l'échéance est passée est signalée « En retard » ;
- filtrer (toutes, actives, terminées), rechercher, tout terminer, supprimer les tâches terminées après confirmation;
- suivre sa progression, utiliser des raccourcis clavier, et retrouver ses tâches après un rechargement de la page.
- La liste est toujours triée : tâches actives d'abord, puis par priorité, puis par échéance.
- Vous allez développer Mes tâches, une liste de tâches (TodoList) complète :


-
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 : …". - Les aides donnent des squelettes de code, jamais la solution complète : c'est à vous de compléter.
- Une règle s'applique à tout le TP : seul
Appmodifie la liste des tâches. Les autres composants reçoivent des données et émettent des événements.


-
Partie 0 : mise en place du projet
⏱ 20 min
Notions mobilisées : Les composants · La syntaxe de base
- Créez le projet, générez les six composants enfants et le dossier du modèle :
npx @angular/cli@latest new tp3-todolist --defaults --ssr=false --style=css
cd tp3-todolist
npx ng generate component formulaire-tache
npx ng generate component barre-filtres
npx ng generate component liste-taches
npx ng generate component item-tache
npx ng generate component progression
npx ng generate component boite-confirmation
mkdir src/app/modeles
npx ng serve
- Dans
app.html, placez un en-tête (<h1>Mes tâches</h1>) et une carte<main class="carte-tp">qui contiendra le formulaire, la barre de filtres et la liste. - Dans
styles.css, définissez les variables de couleur (--bleu,--vert,--rouge,--gris,--bord), un style commun pour les boutons (avec les variantes.secondaire,.dangeret.icone) et la classe.carte-tp. - Dans
app.config.ts, activez le français pour le pipedate:registerLocaleData(localeFr)et{ provide: LOCALE_ID, useValue: 'fr' }.
src/app/
├── app.ts / app.html / app.css ← possède les données (toutes les parties)
├── modeles/tache.ts ← partie 1
├── liste-taches/ item-tache/ ← parties 2, 4 et 6
├── formulaire-tache/ ← partie 3
├── barre-filtres/ ← partie 5
├── progression/ boite-confirmation/ ← partie 7
✅ Point de contrôle
ng servefonctionne, la page affiche le titre « Mes tâches » et la carte blanche.- La console du navigateur (F12) ne contient aucune erreur.
❓ Questions (à répondre dans le README.md)
- Combien de fichiers
ng generate componentcrée-t-il ? À quoi sert chacun ? - Pourquoi crée-t-on six composants au lieu de tout écrire dans
App? Citez deux avantages.
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 : Le typage fort
- Dans
src/app/modeles/tache.ts, sans aucun code Angular :- deux types :
Priorite('haute','normale','basse') etFiltre('toutes','actives','terminees') ; - une interface
Tachedont toutes les propriétés sontreadonly:id,titre,priorite,echeance(string | null, au formatAAAA-MM-JJ),terminee,creeeLe(Date) ; - un type
NouvelleTache, construit avecPick<Tache, …>, qui ne garde que ce que l'utilisateur saisit ; - une interface
Compteurs:toutes,actives,terminees; une constantePRIORITES; - quatre fonctions :
creerTache(n)(identifiantcrypto.randomUUID(), titre nettoyé, non terminée),dateDuJour(decalage = 0),estEnRetard(tache, aujourdhui)ettrierTaches(taches).
- deux types :
export interface Tache {
readonly id: string;
/* à compléter : titre, priorite, echeance, terminee, creeeLe */
}
export type NouvelleTache = Pick<Tache, /* à compléter */>;
// Tâches actives d'abord, puis par priorité, puis par échéance (sans échéance à la fin)
export function trierTaches(taches: readonly Tache[]): Tache[] {
return [...taches].sort((a, b) =>
Number(a.terminee) - Number(b.terminee) || /* priorité */ || /* échéance */);
}
- Les dates au format
AAAA-MM-JJse comparent comme du texte :'2026-10-29' < '2026-10-31'vauttrue. PourdateDuJour(), construisez la chaîne avecgetFullYear(),getMonth() + 1etgetDate(), complétés parpadStart(2, '0').
✅ Point de contrôle
estEnRetardrenvoietruepour une tâche active d'échéance hier,falsesi elle est terminée ou sans échéance.trierTachesplace une tâche « basse » sans échéance après une tâche « haute » datée, et toute tâche terminée en dernier.
readonly, écrire tache.terminee = true est refusé : TS2540: Cannot assign to 'terminee' because it is a read-only property. C'est voulu : on crée toujours une nouvelle tâche ({ ...t, terminee: true }).❓ Questions (à répondre dans le README.md)
- Pourquoi
trierTachescommence-t-il par[...taches]au lieu de trier directement le tableau reçu ? - Pourquoi ne pas utiliser
toISOString().slice(0, 10)pour la date du jour ? (indice : fuseau horaire, autour de minuit) - Quel est l'intérêt de
Pickplutôt que de réécrire une seconde interface ?
git add .
git commit -m "Partie 1 : modèle TypeScript"
-
Partie 2 : afficher la liste avec
input()
⏱ 40 min
Notions mobilisées : Transmettre des données avec input() · La communication entre composants · La syntaxe de base
- Dans
App: un signaltachesinitialisé avec quatre tâches d'exemple (dont une terminée et une en retard), une propriétéaujourdhui = dateDuJour()et uncomputedvisiblesqui renvoie la liste triée.ListeTaches: deux entrées obligatoires,tachesetaujourdhui; un@for … track t.idqui affiche un<app-item-tache>par tâche, et@empty: « Aucune tâche à afficher. » ;ItemTache: entrées obligatoirestacheetaujourdhui; uncomputedenRetard; affichage d'une case à cocher ([checked]), du titre (barré si la tâche est terminée), d'un badge de priorité coloré ([class]), de l'échéance avec le pipedate: 'd MMM', précédée de « En retard · » en rouge si besoin, et de deux boutons ✎ et ✕ (sans action pour l'instant).
export class ItemTache {
readonly tache = input.required<Tache>();
readonly aujourdhui = input.required<string>();
protected readonly enRetard = computed(() => /* à compléter */);
}

✅ Point de contrôle
- Les quatre tâches s'affichent dans l'ordre : « Rendre le TP 2 » (haute, en retard), « Préparer le quiz » (haute), « Tester viewChild » (basse, sans date), puis la tâche terminée, barrée.
- Remplacer le tableau par
[]affiche « Aucune tâche à afficher. ».
[aujourdhui] sur <app-liste-taches> est détecté à la compilation : NG8008: Required input 'aujourdhui' from component ListeTaches must be specified. Et oublier DatePipe dans les imports de ItemTache donne NG8004: No pipe found with name 'date'.❓ Questions (à répondre dans le README.md)
- Pourquoi
enRetardest-il calculé dansItemTacheet non stocké dans la tâche ? - Pourquoi passer
aujourdhuien entrée plutôt que d'appelerdateDuJour()dans chaque ligne ? - À quoi sert
track t.id? Que se passerait-il avectrack $indexquand la liste est triée de nouveau ?
git add .
git commit -m "Partie 2 : affichage de la liste"
-
Partie 3 : ajouter une tâche avec
output()
⏱ 45 min
Notions mobilisées : Émettre des événements avec output() · Les variables de template · La gestion des événements
FormulaireTachene connaît pas la liste : il émet la tâche saisie avecajouter = output<NouvelleTache>().- un
<form>avec trois champs marqués par des variables de référence :#titre,#priorite(une liste générée avec@forsurPRIORITES, « normale » sélectionnée par défaut) et#echeance(type="date") ; (submit)empêche le rechargement ($event.preventDefault()) et appellevalider(titre, priorite, echeance);- un titre de moins de 3 caractères affiche « Le titre doit comporter au moins 3 caractères. » (signal
erreur), effacé dès la frappe suivante ; - sinon : émettre, vider le titre et l'échéance, et redonner le focus au titre pour enchaîner les saisies.
- un
- Dans
App:(ajouter)="ajouter($event)"crée la tâche aveccreerTache()et l'ajoute avecupdate(). Un signalannonceaffiche sous la liste « « … » ajoutée. » (avecaria-live="polite").
protected valider(titre: HTMLInputElement, priorite: HTMLSelectElement, echeance: HTMLInputElement): void {
const texte = titre.value.trim();
// 1. vérifier 2. émettre { titre, priorite, echeance } 3. vider et redonner le focus
}

✅ Point de contrôle
- « ab » + Entrée affiche le message d'erreur, et rien n'est ajouté.
- « Lire la doc des signals », priorité haute, échéance dans quelques jours : la tâche apparaît à sa place dans la liste triée, le champ est vidé et garde le focus.
(ajouter)="ajouter()", sans $event, est refusé si la méthode attend la tâche : TS2554: Expected 1 arguments, but got 0.❓ Questions (à répondre dans le README.md)
- Pourquoi le formulaire émet-il une
NouvelleTacheet non uneTachecomplète ? - Quel est le type de
$eventdans(ajouter)="ajouter($event)"? Et dans(submit)? - Pourquoi transmettre les éléments
#titre,#prioriteet#echeanceà la méthode plutôt que d'utiliser trois signals ?
git add .
git commit -m "Partie 3 : formulaire d'ajout"
-
Partie 4 : cocher et supprimer, relayer les événements
⏱ 40 min
Notions mobilisées : Émettre des événements avec output() · La communication entre composants
- Un clic dans une ligne doit modifier la liste, qui appartient à
App, deux niveaux plus haut. Les sorties ne remontant que d'un niveau, il faut les relayer:ItemTacheémetbasculeretsupprimer, sans valeur : la ligne ne connaît que sa tâche ;ListeTachesrelaie avec ses propres sorties, en ajoutant l'identifiant :(basculer)="basculer.emit(t.id)";Appmodifie la liste avecmap(basculer) etfilter(supprimer), et annonce « « … » supprimée. ».
protected basculer(id: string): void {
this.taches.update((l) => l.map((t) => (t.id === id ? /* nouvelle tâche */ : t)));
}

✅ Point de contrôle
- Cocher « Rendre le TP 2 » la barre, la fait descendre parmi les terminées, et fait disparaître « En retard ».
- ✕ sur une ligne la supprime et affiche l'annonce. Aucune autre ligne ne change.
t.terminee = !t.terminee, si vous avez retiré readonly) ou utiliser push / splice ne crée pas de nouveau tableau : le signal n'est pas prévenu, le tri et les compteurs ne se mettent pas à jour.❓ Questions (à répondre dans le README.md)
- Dessinez le trajet d'un clic sur ✕, de
ItemTachejusqu'àApp, avec le nom de chaque sortie et la valeur transportée. - Pourquoi
ItemTachen'émet-il pas l'identifiant lui-même ? Serait-ce une erreur ? - Quel autre outil du chapitre éviterait de relayer, si les niveaux étaient très nombreux ?
git add .
git commit -m "Partie 4 : cocher et supprimer"
-
Partie 5 : filtres, recherche et
model()
⏱ 40 min
Notions mobilisées : La communication entre composants · Le two-way data binding · Les signals
BarreFiltres:filtre = model.required<Filtre>()etrecherche = model(''): le parent les relie avec[(filtre)]="filtre"et[(recherche)]="recherche";- une entrée
compteurs(Compteurs) : chaque bouton de filtre affiche son nombre (« Actives 3 ») ; le bouton actif est en bleu ; - un champ de recherche
#champqui met à jourrechercheà chaque frappe ; - deux sorties :
toutTermineretviderTerminees, avec deux boutons « Tout terminer » et « Supprimer les terminées (n) », désactivés s'il n'y a rien à faire.
- Dans
App: des signalsfiltreetrecherche, uncomputedcompteurs, etvisiblesqui filtre maintenant selon le filtre et la recherche (sans tenir compte des majuscules) avant de trier. Pour l'instant,(viderTerminees)supprime directement les terminées : la confirmation viendra à la partie 7.

✅ Point de contrôle
- « Actives » n'affiche que les tâches non cochées ; les nombres des trois boutons sont justes et se mettent à jour quand on coche.
- Avec « Actives » et la recherche « tp », seules « Rendre le TP 2 » et « Préparer le quiz sur output() » restent.
- « Tout terminer » coche toutes les tâches puis se désactive.
[(filtre)]="filtre()" (avec parenthèses) est refusé : NG5002: Unsupported expression in a two-way binding. On passe le signal lui-même : [(filtre)]="filtre". Et filtre.set('fini') donne TS2345: Argument of type '"fini"' is not assignable to parameter of type 'Filtre'.❓ Questions (à répondre dans le README.md)
- Écrivez l'équivalent « long » de
[(filtre)]="filtre", avec une entrée et une sortie. - Pourquoi
compteursest-il calculé dansAppet transmis en entrée, plutôt que calculé dansBarreFiltres? - Pourquoi « Supprimer les terminées » est-il une sortie, alors que le filtre est un
model()?
git add .
git commit -m "Partie 5 : filtres et recherche"
-
Partie 6 : modifier une tâche,
linkedSignaletviewChild
⏱ 45 min
Notions mobilisées : Accéder à un composant enfant avec viewChild · Les signals · La gestion des événements · Les variables de template
- Un double-clic sur le titre, ou le bouton ✎, passe la ligne en mode édition (désactivé pour une tâche terminée) :
- un signal
enEdition; avec@if, le titre est remplacé par un champ#champ; - le texte saisi est un brouillon:
brouillon = linkedSignal(() => this.tache().titre), modifiable, et remis au titre reçu si la tâche change ; - Entrée ou la perte du focus (
blur) valident : si le brouillon nettoyé fait au moins 3 caractères et diffère du titre, la ligne émetrenommer(le nouveau titre) ; Échap annule ; - le champ doit recevoir le focus, avec son texte sélectionné, dès son apparition : récupérez-le avec
viewChild<ElementRef<HTMLInputElement>>('champ')(sans.required: il n'existe que pendant l'édition) et uneffect.
- un signal
ListeTachesrelaie :(renommer)="renommer.emit({ id: t.id, titre: $event })";Appmet à jour le titre et annonce « Tâche renommée en « … ». ».
private readonly champ = viewChild<ElementRef<HTMLInputElement>>('champ');
constructor() {
effect(() => {
const c = this.champ()?.nativeElement;
// si le champ existe : lui donner le focus et sélectionner son texte
});
}

✅ Point de contrôle
- Double-clic sur « Tester viewChild sur un formulaire » : le champ apparaît, texte sélectionné ; taper un nouveau titre puis Entrée le renomme.
- Modifier puis Échap : le titre d'origine revient. Vider le champ puis cliquer ailleurs : le titre d'origine revient aussi.
- Le bouton ✎ d'une tâche terminée est grisé.
❓ Questions (à répondre dans le README.md)
- Pourquoi un
linkedSignalplutôt qu'un simplesignal(this.tache().titre)? (indice : NG8118, et le renommage) - Pourquoi la requête
champne peut-elle pas êtreviewChild.required? - Pourquoi l'
effectest-il relancé à chaque passage en mode édition ?
git add .
git commit -m "Partie 6 : modification d'une tâche"
-
Partie 7 : progression et confirmation
⏱ 45 min
Notions mobilisées : Transmettre des données avec input() · Émettre des événements avec output() · Accéder à un composant enfant avec viewChild · Les composants
Progression, composant d'affichage pur, placé dans l'en-tête :- deux entrées
faitesettotal(avecnumberAttribute) ;computed:pourcentetmessage(« Aucune tâche », « 2 restante(s) », « Tout est fait ! ») ; - une barre avec
[style.width.%], verte à 100 %, et les attributsrole="progressbar"et[attr.aria-valuenow].
- deux entrées
BoiteConfirmation, autour de la balise native<dialog>:- entrées
titreetlibelleConfirmer; le message est projeté avec<ng-content />; - une méthode publique
ouvrir(), qui appelleshowModal()sur le<dialog>obtenu parviewChild.required; - Annuler ferme ; le bouton rouge ferme et émet
confirmer.
- entrées
- Dans
App:confirmation = viewChild.required(BoiteConfirmation);(viderTerminees)appelle maintenantdemanderVidage(), qui ouvre la boîte ;(confirmer)supprime les terminées et annonce « 2 tâche(s) terminée(s) supprimée(s). ».
<app-boite-confirmation titre="Supprimer les tâches terminées ?" libelleConfirmer="Supprimer" (confirmer)="viderTerminees()">
<p>{{ compteurs().terminees }} tâche(s) terminée(s) seront supprimées définitivement.</p>
</app-boite-confirmation>

✅ Point de contrôle
- L'en-tête affiche « 1 / 4 terminée(s) · 3 restante(s) » et la barre avance quand on coche ; tout cocher la rend verte avec « Tout est fait ! ».
- « Supprimer les terminées (1) » ouvre la boîte (fond assombri) ; Annuler ou Échap ne suppriment rien ; « Supprimer » vide les terminées.
this.dialogue().showModal() est refusé : TS2339: Property 'showModal' does not exist on type 'ElementRef<HTMLDialogElement>'. La balise est dans nativeElement. ❓ Questions (à répondre dans le README.md)
- Pourquoi le message de la boîte est-il projeté, au lieu d'être une entrée
message? - Pourquoi une entrée
[ouverte]ne suffirait-elle pas pour afficher un<dialog>modal ? Progressionne contient ni sortie ni méthode publique : comment qualifier ce type de composant ?
git add .
git commit -m "Partie 7 : progression et confirmation"
-
Partie 8 : raccourcis clavier et sauvegarde
⏱ 35 min
Notions mobilisées : Accéder à un composant enfant avec viewChild · La gestion des événements · Les signals
- Raccourcis globaux, écoutés dans
Appavechost: { '(document:keydown)': 'clavier($event)' }:
| Touche | Action |
|---|---|
n |
placer le curseur dans le titre du formulaire |
/ |
placer le curseur dans la recherche |
- ignorez les touches tapées dans un champ (sinon on ne pourrait plus écrire un « n ») et les combinaisons avec Ctrl, Alt ou Cmd ;
Appobtient les enfants avecviewChild.required(FormulaireTache)etviewChild.required(BarreFiltres), et appelle leurs méthodes publiquesfocaliser()etfocaliserRecherche()(chacune utilise unviewChildsur son champ) ;- appelez
preventDefault(): sinon le « n » serait aussi tapé dans le champ qui vient de recevoir le focus. - Sauvegarde: un
effectenregistretaches()danslocalStorage(dans untry … catch) ; au démarrage,tachesest initialisé par une méthodelire()qui relit ces données, reconvertitcreeeLeenDate, et renvoie les exemples s'il n'y a rien.
✅ Point de contrôle
- Après un clic dans le vide :
nplace le curseur dans le titre, sans y écrire de « n » ;/place le curseur dans la recherche. - Dans le titre, taper « n » écrit bien la lettre.
- Après F5, les tâches, leur état et leurs titres modifiés sont toujours là.
this.formulaire().focaliser()) est refusé : NG8118: `formulaire` is a required `viewChild` and does not have a value in this context. Dans clavier(), appelé après l'affichage, il n'y a aucun problème.❓ Questions (à répondre dans le README.md)
- Pourquoi
Apppasse-t-il par une méthode de l'enfant au lieu d'accéder directement à son champ ? - Pourquoi l'enregistrement est-il un
effectet non uncomputed? - Que contiendrait
creeeLeaprèsJSON.parsesans reconversion ?
git add .
git commit -m "Partie 8 : clavier et sauvegarde"
-
Partie 9 : variantes et finitions
⏱ 20 min
- Choisissez deux variantes (chaque binôme annonce ses choix au formateur) :
- des catégories (Cours, Maison, Sport…) avec un filtre supplémentaire ;
- un bouton « Annuler la suppression » pendant 5 secondes après chaque suppression ;
- une échéance modifiable en mode édition ;
- un tri au choix (priorité, échéance, date de création, ordre alphabétique) ;
- une confirmation avant de supprimer une tâche non terminée, avec la même
BoiteConfirmation; - une variante libre, validée par le formateur.
- Relisez votre code : membres
protectedouprivatequand ils ne servent pas au parent, noms explicites, aucunconsole.log, aucune erreur dans la console.
git add .
git commit -m "Partie 9 : variantes et finitions"
-
Grille d'évaluation (sur 20)
| Critère | Points |
|---|---|
Parties 0–1 : projet, découpage en composants, modèle typé (readonly, Pick, fonctions) |
2 |
Partie 2 : liste avec input.required, @for, @empty, tri, retard |
2 |
Partie 3 : formulaire, validation, output(), variables de référence |
2 |
| Partie 4 : sorties relayées, mises à jour immuables | 3 |
Partie 5 : filtres et recherche avec model(), compteurs |
2 |
Partie 6 : édition avec linkedSignal, viewChild facultatif, Entrée / Échap / blur |
3 |
Partie 7 : progression, boîte de confirmation (<dialog>, projection, viewChild) |
2 |
Partie 8 : raccourcis clavier par viewChild, sauvegarde |
1 |
| Qualité : un seul composant modifie les données, typage strict, aucune erreur, 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, avec au moins dix commits nommés « Partie N : … ».
- Le fichier
README.md: vos noms, les réponses aux questions, 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 |
|---|---|---|
NG8008: Required input 'aujourdhui' from component ListeTaches must be specified. |
Entrée obligatoire oubliée | Ajouter [aujourdhui]="aujourdhui" |
NG8004: No pipe found with name 'date'. |
DatePipe non importé |
L'ajouter aux imports de ItemTache |
TS2540: Cannot assign to 'terminee' because it is a read-only property. |
Tâche modifiée sur place | { ...t, terminee: !t.terminee } |
TS2554: Expected 1 arguments, but got 0. |
$event oublié dans (ajouter) |
(ajouter)="ajouter($event)" |
NG5002: Unsupported expression in a two-way binding |
[(filtre)]="filtre()" |
[(filtre)]="filtre" |
TS2345: Argument of type '"fini"' is not assignable to parameter of type 'Filtre'. |
Valeur hors de l'union | Utiliser une valeur de Filtre |
TS2339: Property 'showModal' does not exist on type 'ElementRef<HTMLDialogElement>'. |
nativeElement oublié |
this.dialogue().nativeElement.showModal() |
NG8118: `formulaire` is a required `viewChild` and does not have a value in this context. |
Requête lue dans le constructeur | La lire dans une méthode appelée plus tard |
| Cliquer sur ✕ ou cocher ne change rien | Sortie non relayée par ListeTaches, ou tableau modifié sur place |
Relayer avec emit(t.id); map / filter |
| Le champ d'édition n'a pas le focus | Focus demandé avant que le champ existe | viewChild facultatif + effect |
| Taper « n » dans un champ ouvre le formulaire | Touches des champs non ignorées | Tester e.target.tagName |
| Après F5, plus de tâches ou erreur de date | localStorage mal relu |
try … catch et new Date(t.creeeLe) |
-
Pour aller plus loin
- Déplacer la liste et ses méthodes dans un service
TachesServiceinjecté avecinject(): c'est l'objet du chapitre « Les services et l'injection de dépendances ». - Afficher les tâches actives et terminées sur deux pages, avec le routage.
- Écrire des tests unitaires pour
trierTaches()etestEnRetard()avecnpx ng test.
- Déplacer la liste et ses méthodes dans un service
-
Bilan
- Chaque composant a un rôle : saisir, filtrer, afficher une ligne, afficher la liste, montrer la progression, confirmer.
- Un seul composant possède les données ; les autres les reçoivent par
input()et annoncent les actions paroutput(), relayées niveau par niveau. model()crée une liaison bidirectionnelle propre entre un parent et un enfant.viewChild()sert à commander: donner le focus, ouvrir une boîte de dialogue, appeler une méthode publique.- Les mises à jour immuables (
map,filter,...) gardent tous lescomputedjustes.
Une bonne TodoList ne se juge pas au nombre de ses fonctions, mais à la clarté du chemin que suit chaque donnée : du parent qui la possède jusqu'à l'enfant qui l'affiche, et retour.