Angular | TP 2 Développement Full Stack Moderne | 2025/2026
A
ANGULAR 20
TP 2 —Data Binding & Signals & Lifecycle Hooks
🎯 Compétences couvertes :
🧩 Composants Standalone — standalone: true, imports directs
🧩 Data Binding — Interpolation, Property, Event, Two-Way
🧩 @Input() / @Output() — Communication parent ↔ enfant
🧩 Lifecycle Hooks — ngOnInit, ngOnChanges, ngOnDestroy
🧩 Application complète — Gestionnaire de Contacts
🎯 Objectifs pédagogiques
Ce TP ne vous guidera pas à pas à travers les concepts fondamentaux d'Angular 20 vus en séance 2.
À la fin de ces exercices, vous serez capables de :
✅ Créer et utiliser des composants standalone Angular sans NgModule
✅ Appliquer les 4 types de data binding : interpolation, property, event et two-way
✅ Faire communiquer un composant parent et enfant via @Input() et @Output()
✅ Utiliser les lifecycle hooks ngOnInit, ngOnChanges et ngOnDestroy
✅ Construire une application complète de gestion de contacts
✅ Appliquer les bonnes pratiques Angular (nettoyage mémoire, structure)
ILISI1 — 2025/2026 Page 1
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
Création du projet Angular
Ouvrez un terminal et exécutez les commandes suivantes :
# 1. Créer un nouveau projet Angular 20
ng new gestion-contacts
# 2. Répondre aux questions de la CLI :
# - Would you like to enable Server-Side Rendering? --> No
# - Which stylesheet format? --> CSS
# 3. Aller dans le répertoire du projet
cd gestion-contacts
# 4. Générer les composants nécessaires
ng generate component formulaire-contact
ng generate component liste-contacts
# 5. Ouvrir le VS Code
Code .
Structure du projet à obtenir
src/app/
├── [Link] ← Composant racine (parent)
├── [Link]
├── formulaire-contact/
│ ├── [Link] ← Enfant avec @Output
│ └── [Link]
└── liste-contacts/
├── [Link] ← Enfant avec @Input
└── [Link]
ILISI1 — 2025/2026 Page 2
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 1 — Composants
Exercice 1 — Composants 🧩 20 min
Objectif : Comprendre et créer des composants autonomes
Rappel théorique
Chaque composant déclare dans son tableau imports tout ce dont il a besoin (FormsModule, autres
composants...).
Partie A — Analyser un composant existant
Ouvrez le fichier src/app/[Link] et observez sa structure :
// [Link]
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './[Link]',
styleUrl: './[Link]'
})
export class AppComponent {
title = 'gestion-contacts';
}
Questions de compréhension (1/A)
1. À quoi sert le tableau imports dans un composant?
Partie B — Créer et configurer les composants
Ouvrez le fichier [Link] et modifier son contenu par :
// [Link]
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-formulaire-contact',
imports: [FormsModule, CommonModule], // Tout ce qu'on utilisera
templateUrl: './[Link]',
styleUrl: './[Link]',
})
export class FormulaireContact {
titre: string = 'Formulaire de contact';
version: string = 'Angular 20';
}
ILISI1 — 2025/2026 Page 3
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
Créer le template de base
Dans [Link], écrivez :
<!-- [Link] -->
<div>
<h2>{{ titre }}</h2>
<p>Version : {{ version }}</p>
<p>Bonjour depuis le composant formulaire-contact!</p>
</div>
Intégrer le composant dans AppComponent
Dans [Link], importez et utilisez le nouveau composant :
// [Link]
import { Component } from '@angular/core';
import { FormulaireContact } from './formulaire-contact/formulaire-contact';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormulaireContact], // ← Import direct !
template: `
<h1>Gestionnaire de Contacts</h1>
<app-formulaire-contact></app-formulaire-contact>
`
})
export class App { }
✅Résultat attendu
Dans le navigateur, vous devriez voir :
'Gestionnaire de Contacts' (titre de App)
'Formulaire de contact' et 'Version : Angular 21' (depuis FormulaireContact)
ILISI1 — 2025/2026 Page 4
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 2 — Les 4 types de Data Binding
Exercice 2 — Data Binding 🧩 30 min
Objectif : Maîtriser les 4 types de liaison de données Angular
Type Syntaxe Direction Usage principal
1. Interpolation {{ valeur }} TS → HTML Afficher du texte, des calculs
2. Property Binding [src]="url" TS → HTML Propriétés DOM : src, disabled, href
3. Event Binding (click)="fn()" HTML → TS Réagir aux actions utilisateur
4. Two-Way Binding [(ngModel)]="p" TS ↔ HTML Formulaires, champs de saisie
Partie A — Interpolation {{ expression }}
Dans [Link], ajoutez les propriétés suivantes :
export class FormulaireContact {
// Propriétés pour l'interpolation
prenom: string = 'Ahmed';
note: number = 17;
notes: number[] = [14, 16, 18, 12, 17];
dateAujourdhui: string = new Date().toLocaleDateString('fr-FR');
}
Template à compléter
Ajoutez dans le template les expressions d'interpolation suivantes :
<!-- Partie A : Interpolation -->
<section>
<h3>Interpolation</h3>
<!-- 1. Afficher le prénom -->
<p>Bonjour {{ prenom }} !</p>
<!-- 2. Calcul arithmétique -->
<p>Note sur 20 : {{ note }} / 20</p>
<!-- 3. Méthode JavaScript dans l'expression -->
<p>Prénom en majuscules : {{ [Link]() }}</p>
<!-- 4. Opérateur ternaire -->
<p>Réussite : {{ note >= 10 ? '✅ Admis' : '❌ Refusé' }}</p>
<!-- 5. Propriété d'un tableau -->
<p>Nombre de notes : {{ [Link] }}</p>
<!-- 6. Date du jour -->
<p>Date : {{ dateAujourdhui }}</p>
</section>
ILISI1 — 2025/2026 Page 5
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
Questions de réflexion (2/A)
1. Que se passe-t-il si vous écrivez {{ prenom = 'Ali' }} dans le template ?
2. Peut-on utiliser une boucle for dans une interpolation ? Pourquoi ?
3. Quelle est la différence entre {{ notes }} et {{ [Link] }} ?
Partie B — Property Binding [propriete]="valeur"
Ajoutez ces propriétés dans le composant :
imageUrl: string = '[Link]
boutonActif: boolean = false;
tailleTexte: number = 18;
classeCSS: string = 'alerte-info';
Puis dans le template :
<!-- Partie B : Property Binding -->
<section>
<h3>Property Binding</h3>
<!-- Lier src d'une image -->
<img [src]="imageUrl" [alt]="'Photo de ' + prenom" width="200">
<!-- Bouton dynamiquement actif/inactif -->
<button [disabled]="!boutonActif">Bouton contrôlé</button>
<!-- Pour tester : changez boutonActif à true dans le .ts -->
<!-- Style dynamique -->
<p [[Link]]="tailleTexte">
Ce texte a une taille dynamique de {{ tailleTexte }}px
</p>
<!-- Classe CSS dynamique -->
<div [class]="classeCSS">
Bloc avec classe CSS dynamique : {{ classeCSS }}
</div>
</section>
Partie C — Event Binding (evenement)="methode()"
Ajoutez ces propriétés et méthodes dans le composant :
compteur: number = 0;
couleurBouton: string = 'gris';
texteInput: string = '';
incrementer(): void {
[Link]++;
}
decrementer(): void {
if ([Link] > 0) [Link]--;
}
ILISI1 — 2025/2026 Page 6
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
reinitialiser(): void {
[Link] = 0;
}
changerCouleur(c: string): void {
[Link] = c;
}
onFrappe(event: Event): void {
const input = [Link] as HTMLInputElement;
[Link] = [Link];
}
Template correspondant :
<!-- Partie C : Event Binding -->
<section>
<h3>Event Binding</h3>
<!-- Compteur avec 3 boutons -->
<button (click)="decrementer()">-</button>
<span> {{ compteur }} </span>
<button (click)="incrementer()">+</button>
<button (click)="reinitialiser()">Reset</button>
<!-- Passer un paramètre à la méthode -->
<br><br>
<button (click)="changerCouleur('rouge')">Rouge</button>
<button (click)="changerCouleur('bleu')">Bleu</button>
<button (click)="changerCouleur('vert')">Vert</button>
<p>Couleur choisie : {{ couleurBouton }}</p>
<!-- Récupérer l'événement $event -->
<input (keyup)="onFrappe($event)" placeholder="Tapez quelque chose...">
<p>Vous écrivez : {{ texteInput }}</p>
</section>
Partie D — Two-Way Binding [(ngModel)]
Prérequis obligatoire
Pour utiliser [(ngModel)], FormsModule doit être dans le tableau imports de votre composant.
Vérifiez que imports: [FormsModule, CommonModule] est bien présent dans @Component.
Ajoutez ces propriétés :
// Propriétés liées au formulaire
nom: string = '';
email: string = '';
telephone: string = '';
age: number = 0;
Template :
<!-- Partie D : Two-Way Binding -->
<section>
<h3>Two-Way Binding — Aperçu en temps réel</h3>
ILISI1 — 2025/2026 Page 7
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
<input [(ngModel)]="nom" placeholder="Nom complet">
<input [(ngModel)]="email" type="email" placeholder="Adresse email">
<input [(ngModel)]="telephone" placeholder="Téléphone">
<input [(ngModel)]="age" type="number" placeholder="Âge">
<!-- Aperçu en temps réel -->
<div>
<h4>Aperçu :</h4>
<p><strong>{{ nom }}</strong></p>
<p>{{ email }} | {{ telephone }}</p>
<p>Age : {{ age }} ans</p>
<!-- Bonus : afficher uniquement si nom rempli -->
<p *ngIf="[Link] > 0">
Bonjour {{ nom }}, votre saisie est en cours !
</p>
</div>
</section>
✅Résultat attendu
Chaque frappe au clavier met à jour instantanément l'aperçu.
Le compteur s'incrémente/décrémente au clic.
L'image se charge depuis l'URL définie dans le composant.
ILISI1 — 2025/2026 Page 8
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 3 — Communication @Input / @Output
Exercice 3 — Communication entre composants 🧩 25 min
Objectif : Faire dialoguer un composant parent et un composant enfant
Rappel — Mécanisme
@Input () : Le parent envoie une donnée à l'enfant via [propriete]="valeur"
@Output() : L'enfant notifie le parent via un EventEmitter et (event)="methode($event)"
Partie A — Définir l'interface Contact
Créez un fichier src/app/[Link] (réutilisable dans tous les composants) :
// [Link]
export interface Contact {
nom: string;
email: string;
telephone: string;
}
Partie B — @Output() dans FormulaireContactComponent
Modifiez [Link] pour émettre un contact vers le parent :
// [Link]
import { Component, Output, EventEmitter } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { Contact } from '../[Link]';
@Component({
selector: 'app-formulaire-contact',
imports: [FormsModule, CommonModule],
templateUrl: './[Link]',
})
export class FormulaireContactComponent {
nom: string = '';
email: string = '';
telephone: string = '';
// Déclarer l'événement à émettre
@Output() contactSauvegarde = new EventEmitter<Contact>();
sauvegarder(): void {
// Vérifier que les champs obligatoires sont remplis
if ([Link]() && [Link]()) {
// Émettre le contact vers le parent
[Link]({
nom: [Link],
email: [Link],
telephone: [Link]
ILISI1 — 2025/2026 Page 9
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
});
// Réinitialiser le formulaire
[Link] = [Link] = [Link] = '';
} else {
alert('Veuillez remplir le nom et l\'email.');
}
}
}
Template du formulaire avec bouton
<!-- [Link] -->
<div>
<h2>Ajouter un Contact</h2>
<input [(ngModel)]="nom" placeholder="Nom complet *">
<input [(ngModel)]="email" type="email" placeholder="Email *">
<input [(ngModel)]="telephone" placeholder="Téléphone">
<!-- Aperçu conditionnel -->
<div *ngIf="nom">
<strong>Aperçu :</strong> {{ nom }} — {{ email }}
</div>
<!-- Bouton déclenche sauvegarder() -->
<button (click)="sauvegarder()">
➕ Ajouter le contact
</button>
</div>
Partie C — @Input() dans ListeContacts
Modifiez [Link] pour recevoir la liste du parent :
// [Link]
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Contact } from '../[Link]';
@Component({
selector: 'app-liste-contacts',
standalone: true,
imports: [CommonModule], // Nécessaire pour *ngIf et *ngFor
templateUrl: './[Link]',
})
export class ListeContacts {
// Reçoit le tableau de contacts depuis le parent
@Input() contacts: Contact[] = [];
}
Template de la liste
<!-- [Link] -->
<div>
<h2>Liste des Contacts ({{ [Link] }})</h2>
ILISI1 — 2025/2026 Page 10
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
<!-- Message si liste vide -->
<p *ngIf="[Link] === 0">
Aucun contact pour le moment.
</p>
<!-- Affichage si liste non vide -->
<ul *ngIf="[Link] > 0">
<li *ngFor="let c of contacts; let i = index">
<span>{{ i + 1 }}.</span>
<strong> {{ [Link] }} </strong>
— {{ [Link] }}
<small *ngIf="[Link]"> | {{ [Link] }}</small>
</li>
</ul>
</div>
Partie D — Assembler dans AppComponent (composant parent)
// [Link]
import { Component } from '@angular/core';
import { FormulaireContact}
from './formulaire-contact/[Link]';
import { ListeContactsComponent }
from './liste-contacts/[Link]';
import { Contact } from './[Link]';
@Component({
selector: 'app-root',
standalone: true,
imports: [FormulaireContactComponent, ListeContactsComponent],
template: `
<!-- [Link] -->
<div>
<h2>Liste des Contacts ({{ [Link] }})</h2>
<!-- Message si liste vide -->
<p *ngIf="[Link] === 0">
Aucun contact pour le moment.
</p>
<!-- Affichage si liste non vide -->
<ul *ngIf="[Link] > 0">
<li *ngFor="let c of contacts; let i = index">
<span>{{ i + 1 }}.</span>
<strong> {{ [Link] }} </strong>
— {{ [Link] }}
<small *ngIf="[Link]"> | {{ [Link] }}</small>
</li>
</ul>
</div> `
})
export class AppComponent {
mesContacts: Contact[] = [];
ajouterContact(contact: Contact): void {
[Link](contact);
[Link]('Contact ajouté :', contact);
ILISI1 — 2025/2026 Page 11
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
}
}
✅Résultat attendu
1. Saisir un nom et un email dans le formulaire, puis cliquer 'Ajouter'.
2. Le contact apparaît immédiatement dans la liste en dessous.
3. Le formulaire se vide après la sauvegarde.
4. Le compteur dans le titre de la liste s'incrémente.
Questions de compréhension (3/D)
1. Quel composant est le 'parent' ? Quel composant est 'l'enfant' ici ?
2. Que se passe-t-il si on oublie d'ajouter (contactSauvegarde)="ajouterContact($event)" dans le template
parent ?
3. Pourquoi utilise-t-on $event dans (contactSauvegarde)="ajouterContact($event)" ?
4. Que se passe-t-il si l'on passe [contacts]="[]" au composant liste ?
ILISI1 — 2025/2026 Page 12
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 4 — Lifecycle Hooks
Exercice 4 — Cycle de vie des composants 🧩 25 min
Objectif : Utiliser ngOnInit, ngOnChanges et ngOnDestroy
Hook Interface Quand ? Usage typique Fréquence
constructor() — À la création Injection de services 1 fois
UNIQUEMENT
ngOnChanges() OnChanges @Input() change Réagir aux nouvelles N fois
entrées
ngOnInit() ⭐ OnInit Après 1er affichage Charger données, HTTP 1 fois
ngAfterViewInit() AfterViewInit Vue HTML rendue Accéder au DOM 1 fois
ngOnDestroy() OnDestroy Avant destruction Unsubscribe, libérer 1 fois
mémoire
Partie A — ngOnInit dans ListeContactsComponent
Modifiez ListeContactsComponent pour implémenter OnInit :
// [Link]
import { Component, Input, OnInit, OnDestroy }
from '@angular/core';
import { CommonModule } from '@angular/common';
import { Contact } from '../[Link]';
@Component({
selector: 'app-liste-contacts',
standalone: true,
imports: [CommonModule],
templateUrl: './[Link]',
})
export class ListeContactsComponent
implements OnInit, OnDestroy {
@Input() contacts: Contact[] = [];
dateChargement: string = '';
// constructor : UNIQUEMENT pour l'injection de services
constructor() {
[Link]('[1] constructor() appelé');
// NE PAS accéder aux @Input() ici - ils ne sont pas encore remplis !
}
// ngOnInit : tout le reste de l'initialisation
ngOnInit(): void {
[Link]('[2] ngOnInit() appelé');
[Link](` Contacts reçus : ${[Link]}`);
// Enregistrer l'heure de chargement
[Link] = new Date().toLocaleTimeString('fr-FR');
}
ILISI1 — 2025/2026 Page 13
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
// ngOnDestroy : libérer les ressources
ngOnDestroy(): void {
[Link]('[3] ngOnDestroy() appelé — nettoyage');
}
}
🎯 Vérification dans la console
Ouvrez Chrome DevTools (F12) > onglet Console.
Rechargez la page : vous devez voir [1] constructor() puis [2] ngOnInit().
L'ordre d'appel des hooks est toujours le même.
Partie B — ngOnChanges pour détecter les changements d'@Input
Ajoutez ngOnChanges dans ListeContactsComponent pour réagir à chaque nouveau contact :
// Ajouter à l'import : OnChanges, SimpleChanges
import { Component, Input, OnInit, OnChanges,
OnDestroy, SimpleChanges } from '@angular/core';
export class ListeContactsComponent
implements OnInit, OnChanges, OnDestroy {
// ... (propriétés existantes) ...
nombreAjouts: number = 0;
// Appelé à CHAQUE fois que contacts[] change
ngOnChanges(changes: SimpleChanges): void {
if (changes['contacts']) {
const avant = changes['contacts'].previousValue;
const apres = changes['contacts'].currentValue;
const premier = changes['contacts'].firstChange;
[Link]('ngOnChanges() appelé');
[Link](' Premier appel ?', premier);
[Link](' Avant :', avant?.length ?? 0, 'contact(s)');
[Link](' Après :', apres?.length ?? 0, 'contact(s)');
if (!premier) [Link]++;
}
}
}
Affichez le compteur dans le template :
<!-- Ajouter dans [Link] -->
<p *ngIf="nombreAjouts > 0">
{{ nombreAjouts }} contact(s) ajouté(s) depuis le chargement
</p>
<small>Liste chargée à : {{ dateChargement }}</small>
Partie C — Pré-remplir la liste avec ngOnInit dans AppComponent
ILISI1 — 2025/2026 Page 14
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
Dans AppComponent, utilisez ngOnInit pour initialiser des contacts de démo :
// [Link]
import { Component, OnInit } from '@angular/core';
import { Contact } from './[Link]';
// ... autres imports ...
export class AppComponent implements OnInit {
mesContacts: Contact[] = [];
// constructor : injection uniquement (vide ici)
constructor() { }
// ngOnInit : initialisation des données
ngOnInit(): void {
// Pré-remplir avec 2 contacts démo
[Link] = [
{ nom: 'Ali Benali', email: 'ali@[Link]', telephone: '0600000001' },
{ nom: 'Sara Alami', email: 'sara@[Link]', telephone: '0600000002' },
];
[Link]('AppComponent initialisé avec', [Link], 'contacts');
}
ajouterContact(contact: Contact): void {
[Link] = [...[Link], contact]; // Nouvelle référence !
}
}
🎯 Astuce importante
Notez l'utilisation de [...[Link], contact] au lieu de .push().
Avec push(), Angular ne détecte pas toujours le changement de tableau.
Créer un nouveau tableau garantit que ngOnChanges est bien déclenché.
Questions de compréhension (4)
1. Pourquoi ne doit-on PAS faire d'appel HTTP dans le constructor() ?
2. Combien de fois ngOnChanges() est-il appelé lors du pré-remplissage initial ?
3. Que se passerait-il si on oublie d'implémenter OnDestroy et qu'on a des subscriptions ?
4. Dans quel ordre sont appelés les hooks au chargement initial de la page ?
ILISI1 — 2025/2026 Page 15
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 5 — Fonctionnalités Avancées
Exercice 5 — Améliorations et bonnes pratiques 🧩 20 min
Objectif : Enrichir l'application avec des fonctionnalités réalistes
Partie A — Supprimer un contact
Dans AppComponent, ajoutez la méthode de suppression :
// [Link]
supprimerContact(index: number): void {
[Link] = [Link]((_, i) => i !== index);
[Link](`Contact ${index} supprimé. Reste : ${[Link]}`);
}
Dans le template AppComponent, transmettez la méthode via @Output :
<!-- [Link] -->
<app-liste-contacts
[contacts]="mesContacts"
(contactSupprime)="supprimerContact($event)">
</app-liste-contacts>
Dans ListeContactsComponent, ajoutez un @Output pour la suppression :
// [Link]
@Output() contactSupprime = new EventEmitter<number>();
supprimer(index: number): void {
if (confirm('Confirmer la suppression ?')) {
[Link](index);
}
}
<!-- [Link] -->
<li *ngFor="let c of contacts; let i = index">
{{ i+1 }}. <strong>{{ [Link] }}</strong> — {{ [Link] }}
<button (click)="supprimer(i)">❌</button>
</li>
Partie B — Recherche en temps réel
Dans ListeContactsComponent, ajoutez un champ de recherche :
// [Link]
recherche: string = '';
get contactsFiltres(): Contact[] {
if (![Link]()) return [Link];
const terme = [Link]();
return [Link](c =>
[Link]().includes(terme) ||
[Link]().includes(terme)
);
ILISI1 — 2025/2026 Page 16
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
<!-- [Link] -->
<input [(ngModel)]="recherche"
placeholder="🔍 Rechercher un contact...">
<p>{{ [Link] }} résultat(s)</p>
<!-- Utiliser contactsFiltres au lieu de contacts dans *ngFor -->
<li *ngFor="let c of contactsFiltres; let i = index">
...
</li>
🎯 Attention !
Comme vous ajoutez [(ngModel)] dans ListeContactsComponent, il faut ajouter FormsModule dans son
tableau imports !
imports: [CommonModule, FormsModule]
Partie C — Compteur et statistiques
Enrichissez l'aperçu avec des statistiques dans AppComponent :
// [Link]
get nombreContacts(): number {
return [Link];
}
get messageStatut(): string {
if ([Link] === 0) return 'Carnet vide';
if ([Link] === 1) return '1 contact';
return `${[Link]} contacts`;
}
<!-- [Link] -->
<header>
<h1>🔍 Gestionnaire de Contacts</h1>
<span>{{ messageStatut }}</span>
</header>
ILISI1 — 2025/2026 Page 17
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 EXERCICE 6 — Application complète
Exercice 6 — Projet final — Gestion de Contacts 🧩 20 min
Objectif : Assembler tous les concepts dans une application fonctionnelle et stylisée
Rappel — Architecture finale
AppComponent (parent)
├── FormulaireContactComponent (enfant) : @Output contactSauvegarde
└── ListeContactsComponent (enfant) : @Input contacts, @Output contactSupprime
Données partagées : Contact { nom, email, telephone }
Logique initiale dans : ngOnInit() du AppComponent
Nettoyage dans : ngOnDestroy() si nécessaire
Code CSS de base pour le style
Ajoutez dans src/[Link] pour un rendu minimal propre :
/* [Link] */
* { box-sizing: border-box; font-family: Arial, sans-serif; }
body { margin: 0; padding: 20px; background: #f0f2f5; }
h1 { color: #DD0031; border-bottom: 3px solid #DD0031; }
h2 { color: #1A1A2E; }
h3 { color: #495057; }
input {
display: block; margin: 8px 0;
padding: 10px; width: 300px;
border: 2px solid #ddd; border-radius: 4px;
}
input:focus { outline: none; border-color: #DD0031; }
button {
padding: 10px 20px; margin: 8px 4px;
background: #DD0031; color: white;
border: none; border-radius: 4px; cursor: pointer;
}
button:hover { background: #B0001F; }
ul { list-style: none; padding: 0; }
li {
padding: 12px; margin: 6px 0;
background: white; border-radius: 4px;
border-left: 4px solid #DD0031;
display: flex; align-items: center; gap: 8px;
}
.apercu {
padding: 10px; background: #EDE7F6;
border-radius: 4px; margin-top: 10px;
}
ILISI1 — 2025/2026 Page 18
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
Checklist de vérification finale
✓ Critère de validation
☐ Les 3 composants sont standalone (standalone: true)
☐ Le formulaire utilise [(ngModel)] pour les 3 champs
☐ L'aperçu en temps réel fonctionne pendant la saisie
☐ @Output contactSauvegarde émet un objet Contact complet
☐ @Input contacts reçoit et affiche le tableau correctement
☐ Ajout d'un contact met à jour la liste instantanément
☐ ngOnInit() est implémenté et pré-remplit la liste
☐ ngOnChanges() détecte chaque ajout de contact
☐ ngOnDestroy() est implémenté dans ListeContactsComponent
☐ La console F12 affiche les messages de logs aux bons moments
☐ La recherche filtre les contacts en temps réel
☐ La suppression d'un contact fonctionne avec confirmation
ILISI1 — 2025/2026 Page 19
Angular | TP 2 Développement Full Stack Moderne | 2025/2026
🎯 Erreurs fréquentes et solutions
❌Erreur rencontrée 🎯 Cause ✅Solution
Can't bind to 'ngModel' FormsModule non importé Ajouter FormsModule dans imports:
[]
*ngIf / *ngFor inconnu CommonModule non importé Ajouter CommonModule dans
imports: []
$event non reçu Mauvaise syntaxe d'écoute Vérifier (event)="fn($event)"
@Input vide au chargement Accès dans constructor() Déplacer dans ngOnInit()
Liste non mise à jour Utilisation de .push() Utiliser [...tableau, item]
ngOnInit non appelé Interface non implementée Ajouter implements OnInit
Fuite mémoire (Observable) Pas d'unsubscribe() Implémenter ngOnDestroy()
🎯 Récapitulatif — Ce que vous avez pratiqué
Ex. Concept Ce que vous avez appris
1 Standalone standalone: true, imports directs, absence de NgModule
2 Data Binding {{ }}, [ ], ( ), [( )] — les 4 syntaxes et leurs directions
3 @Input / @Output Communication parent↔enfant, EventEmitter, $event
4 Lifecycle Hooks ngOnInit, ngOnChanges, ngOnDestroy et leurs interfaces
5 Avancé Suppression, recherche, propriétés calculées (getter)
6 Projet final Assemblage complet, CSS, architecture en composants
ILISI1 — 2025/2026 Page 20