0% ont trouvé ce document utile (0 vote)
0 vues20 pages

TP2

Ce document présente un TP sur Angular 20, axé sur le data binding, les composants standalone, et les lifecycle hooks. Les étudiants apprendront à créer une application de gestion de contacts en utilisant des concepts tels que @Input() et @Output() pour la communication entre composants. Les exercices incluent la création de composants, l'application des différents types de data binding, et la gestion de la communication parent-enfant.

Transféré par

knouziayoub2
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
0 vues20 pages

TP2

Ce document présente un TP sur Angular 20, axé sur le data binding, les composants standalone, et les lifecycle hooks. Les étudiants apprendront à créer une application de gestion de contacts en utilisant des concepts tels que @Input() et @Output() pour la communication entre composants. Les exercices incluent la création de composants, l'application des différents types de data binding, et la gestion de la communication parent-enfant.

Transféré par

knouziayoub2
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

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

Vous aimerez peut-être aussi