Components
Component Basique
Section intitulée « Component Basique »import { Component } from '@angular/core';
@Component({ selector: 'app-album', standalone: true, template: `<h1>{{ title }}</h1>`, styles: [`h1 { color: blue; }`]})export class AlbumComponent { title = 'Now Playing';}Utilisation:
<app-album></app-album>input() - Parent vers Enfant
Section intitulée « input() - Parent vers Enfant »import { Component, input } from '@angular/core';
@Component({ selector: 'app-album-card', standalone: true, template: ` <div class="card"> <h2>{{ name() }}</h2> <p>Artist: {{ artist() }}</p> <p class="price">{{ price() | currency }}</p> </div> `})export class AlbumCardComponent { // Entrées basées sur les signaux (Angular 17+) name = input<string>(''); // Optionnel avec valeur par défaut artist = input<string>(''); // Optionnel avec valeur par défaut price = input<number>(0); // Optionnel avec valeur par défaut}import { Component } from '@angular/core';import { AlbumCardComponent } from './album-card.component';
@Component({ selector: 'app-parent', standalone: true, imports: [AlbumCardComponent], template: ` <app-album-card [name]="albumName" [artist]="albumArtist" [price]="albumPrice"> </app-album-card> `})export class ParentComponent { albumName = 'Dark Side of the Moon'; albumArtist = 'Pink Floyd'; albumPrice = 29.99;}Étape 1: L’enfant déclare les entrées
name = input<string>(''); // Entrée basée sur les signauxartist = input<string>('');price = input<number>(0);Étape 2: Le parent transmet les données
<app-album-card [name]="albumName" [artist]="albumArtist" [price]="albumPrice"></app-album-card>Étape 3: L’enfant lit les entrées comme des signaux
<h2>{{ name() }}</h2> <!-- Appeler comme une fonction --><p>Artist: {{ artist() }}</p><p>{{ price() | currency }}</p>Différence clé par rapport à @Input():
- ✅ Les entrées sont des signaux - appeler avec
() - ✅ Réactif par défaut
- ✅ Peut être utilisé dans computed(), effect()
input() avec Requis
Section intitulée « input() avec Requis »import { Component, input } from '@angular/core';
@Component({ selector: 'app-album', standalone: true, template: ` <h3>{{ name() }}</h3> <p>Artist: {{ artist() }}</p> <p *ngIf="featured()">⭐ Featured Album!</p> `})export class AlbumComponent { // Input requis - pas de valeur par défaut name = input.required<string>();
// Inputs optionnels avec valeurs par défaut artist = input<string>('Unknown Artist'); featured = input<boolean>(false);}<!-- ✅ Valide - title est fourni --><app-album [title]="'Abbey Road'" [artist]="'The Beatles'" [featured]="true"></app-album>
<!-- ❌ Erreur de compilation - title est requis --><app-album [artist]="'The Beatles'"></app-album>Avantages:
- Vérification à la compilation pour les entrées requises
- Pas besoin de
!(assertion non-null) - Sûreté des types
output() - Enfant vers Parent
Section intitulée « output() - Enfant vers Parent »import { Component, output } from '@angular/core';
@Component({ selector: 'app-track-player', standalone: true, template: ` <button (click)="play()">▶️ Play Count: {{ playCount }}</button> `})export class TrackPlayerComponent { playCount = 0;
// Sortie basée sur les signaux (Angular 17.1+) played = output<number>();
play() { this.playCount++; this.played.emit(this.playCount); // Émettre vers le parent }}import { Component } from '@angular/core';import { TrackPlayerComponent } from './track-player.component';
@Component({ selector: 'app-parent', standalone: true, imports: [TrackPlayerComponent], template: ` <app-track-player (played)="onTrackPlayed($event)"></app-track-player> <p>Total plays: {{ totalPlays }}</p> `})export class ParentComponent { totalPlays = 0;
onTrackPlayed(playCount: number) { this.totalPlays = playCount; console.log('Track played, total:', playCount); }}1. Click play button in Child ↓2. Child calls play() ↓3. Child emits: played.emit(this.playCount) ↓4. Parent receives: (played)="onTrackPlayed($event)" ↓5. Parent's onTrackPlayed() is called with play count ↓6. Parent updates: this.totalPlays = playCountDifférences clés par rapport à @Output():
- ✅ Pas besoin d’importer
EventEmitter - ✅ Syntaxe plus simple:
output<T>() - ✅ Sûreté des types par défaut
input() + output() Combinés
Section intitulée « input() + output() Combinés »import { Component, input, output } from '@angular/core';
interface Album { id: number; name: string; artist: string; description: string; price: number; tags: string[]; highlighted?: boolean;}
@Component({ selector: 'app-album-editor', standalone: true, template: ` <input [value]="album().name" (input)="onNameChange($event)" placeholder="Album name"> <input [value]="album().artist" (input)="onArtistChange($event)" placeholder="Artist name"> <input type="number" [value]="album().price" (input)="onPriceChange($event)" placeholder="Price"> <button (click)="onSave()">💾 Save</button> <button (click)="onCancel()">❌ Cancel</button> `})export class AlbumEditorComponent { // Entrée basée sur les signaux (requise) album = input.required<Album>();
// Sorties basées sur les signaux save = output<Album>(); cancel = output<void>();
onNameChange(event: Event) { const input = event.target as HTMLInputElement; const updated = { ...this.album(), name: input.value }; this.save.emit(updated); }
onArtistChange(event: Event) { const input = event.target as HTMLInputElement; const updated = { ...this.album(), artist: input.value }; this.save.emit(updated); }
onPriceChange(event: Event) { const input = event.target as HTMLInputElement; const updated = { ...this.album(), price: parseFloat(input.value) }; this.save.emit(updated); }
onSave() { this.save.emit(this.album()); }
onCancel() { this.cancel.emit(); }}import { Component, signal } from '@angular/core';import { AlbumEditorComponent } from './album-editor.component';
@Component({ selector: 'app-parent', standalone: true, imports: [AlbumEditorComponent], template: ` <app-album-editor [album]="currentAlbum()" (save)="handleSave($event)" (cancel)="handleCancel()"> </app-album-editor> `})export class ParentComponent { currentAlbum = signal<Album>({ id: 1, name: 'Dark Side of the Moon', artist: 'Pink Floyd' });
handleSave(updatedAlbum: Album) { this.currentAlbum.set(updatedAlbum); console.log('Album saved:', updatedAlbum); }
handleCancel() { console.log('Edit cancelled'); }}Utiliser les Entrées dans computed()
Section intitulée « Utiliser les Entrées dans computed() »import { Component, input, computed } from '@angular/core';
@Component({ selector: 'app-album-duration', standalone: true, template: ` <div> <p>🎵 Tracks: {{ trackCount() }}</p> <p>⏱️ Avg Duration: {{ avgDuration() }}s</p> <p>⏰ Total: {{ totalDuration() }}s ({{ formattedDuration() }})</p> </div> `})export class AlbumDurationComponent { trackCount = input<number>(0); avgDuration = input<number>(0);
// Signal calculé dérivé des entrées totalDuration = computed(() => { return this.trackCount() * this.avgDuration(); });
formattedDuration = computed(() => { const total = this.totalDuration(); const mins = Math.floor(total / 60); const secs = total % 60; return `${mins}:${secs.toString().padStart(2, '0')}`; });}<app-album-duration [trackCount]="12" [avgDuration]="240"></app-album-duration>
<!-- Sortie: 🎵 Tracks: 12 ⏱️ Avg Duration: 240s ⏰ Total: 2880s (48:00)-->Avantages:
totalDuration()etformattedDuration()se mettent à jour automatiquement quand les entrées changent- Pas besoin du hook de cycle de vie ngOnChanges
- Déclaratif et réactif
Utiliser les Entrées dans effect()
Section intitulée « Utiliser les Entrées dans effect() »import { Component, input, effect } from '@angular/core';
@Component({ selector: 'app-track-analytics', standalone: true, template: `<p>Now Playing: Track #{{ trackId() }}</p>`})export class TrackAnalyticsComponent { trackId = input<number>(0);
constructor() { // L'effet s'exécute quand trackId() change effect(() => { console.log('Track changed to:', this.trackId()); // Effet secondaire: log vers analytics, mise à jour historique, etc. }); }}Transformations d’Entrées
Section intitulée « Transformations d’Entrées »import { Component, input } from '@angular/core';
@Component({ selector: 'app-album-formatter', standalone: true, template: ` <p>Artist: {{ formattedArtist() }}</p> <p>Year: {{ releaseYear() }}</p> <p>Featured: {{ isFeatured() }}</p> `})export class AlbumFormatterComponent { // Transformer string en majuscules formattedArtist = input('', { transform: (value: string) => value.toUpperCase() });
// Transformer string en nombre releaseYear = input(0, { transform: (value: string | number) => typeof value === 'string' ? parseInt(value, 10) : value });
// Transformer string en booléen isFeatured = input(false, { transform: (value: string | boolean) => value === 'true' || value === true });}<!-- Passage des valeurs --><app-album-formatter formattedArtist="pink floyd" releaseYear="1973" isFeatured="true"></app-album-formatter>
<!-- Transformations appliquées: Artist: PINK FLOYD (uppercase) Year: 1973 (parsed to number) Featured: true (parsed to boolean)-->Alias d’Entrées
Section intitulée « Alias d’Entrées »import { Component, input } from '@angular/core';
@Component({ selector: 'app-aliased', standalone: true, template: `<p>{{ artistName() }}</p>`})export class AliasedComponent { // Nom de propriété: artistName // Liaison dans le template: [artist-name] artistName = input<string>('', { alias: 'artist-name' });}Utilisation:
<app-aliased [artist-name]="'Pink Floyd'"></app-aliased>Modèles de Communication entre Composants
Section intitulée « Modèles de Communication entre Composants »Modèle 1: Parent-Enfant Simple
Section intitulée « Modèle 1: Parent-Enfant Simple »// Enfant@Component({ selector: 'app-track-display', template: `<p>🎵 {{ trackTitle() }}</p>`})export class TrackDisplayComponent { trackTitle = input<string>('');}
// Parent@Component({ template: `<app-track-display [trackTitle]="'Bohemian Rhapsody'"></app-track-display>`})export class ParentComponent {}Modèle 2: L’Enfant Notifie le Parent
Section intitulée « Modèle 2: L’Enfant Notifie le Parent »// Enfant@Component({ selector: 'app-play-button', template: `<button (click)="play()">▶️ Play</button>`})export class PlayButtonComponent { trackPlayed = output<void>();
play() { this.trackPlayed.emit(); }}
// Parent@Component({ template: `<app-play-button (trackPlayed)="onTrackPlayed()"></app-play-button>`})export class ParentComponent { onTrackPlayed() { console.log('Track is now playing!'); }}Modèle 3: Flux de Données Bidirectionnel
Section intitulée « Modèle 3: Flux de Données Bidirectionnel »// Enfant@Component({ selector: 'app-track-editor', template: ` <input [value]="title()" (input)="titleChange.emit($any($event.target).value)" placeholder="Track title"> `})export class TrackEditorComponent { title = input<string>(''); titleChange = output<string>();}
// Parent@Component({ template: `<app-track-editor [(title)]="trackTitle"></app-track-editor>`})export class ParentComponent { trackTitle = signal('Stairway to Heaven');}Bonnes Pratiques
Section intitulée « Bonnes Pratiques »- ✅ Utiliser
input()au lieu de@Input()- Basé sur les signaux, plus réactif - ✅ Utiliser
output()au lieu de@Output()- Plus simple, pas besoin d’importer EventEmitter - ✅ Utiliser
input.required()pour les entrées requises - Sécurité à la compilation - ✅ Accéder aux entrées comme des fonctions:
this.name()- Ce sont des signaux - ✅ Utiliser computed() pour les valeurs dérivées - Réactivité automatique
- ✅ Utiliser effect() pour les effets secondaires - Réagit aux changements d’entrées
- ✅ Utiliser les transformations pour la conversion de types - Séparation claire des préoccupations
- ❌ Ne pas muter directement les valeurs input() - Les entrées sont en lecture seule
- ❌ Ne pas utiliser ngOnChanges avec les entrées basées sur signaux - Utiliser computed() ou effect()
input() vs @Input()
Section intitulée « input() vs @Input() »| Fonctionnalité | @Input() (Ancien) |
input() (Moderne) |
|---|---|---|
| Type | Propriété | Signal |
| Accès | this.name |
this.name() |
| Réactivité | Manuelle (ngOnChanges) | Automatique |
| Requis | @Input({ required: true }) |
input.required<T>() |
| Défaut | @Input() x = 5 |
input<number>(5) |
| Transform | @Input({ transform }) |
input(0, { transform }) |
| Fonctionne avec | Détection de changements | Signals, computed(), effect() |
output() vs @Output()
Section intitulée « output() vs @Output() »| Fonctionnalité | @Output() (Ancien) |
output() (Moderne) |
|---|---|---|
| Import | EventEmitter | Aucun nécessaire |
| Déclaration | new EventEmitter<T>() |
output<T>() |
| Émettre | .emit(value) |
.emit(value) |
| Sûreté des types | Manuelle | Automatique |

