Aller au contenu principal

Components

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

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
}

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);
}

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
}
}

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();
}
}

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')}`;
});
}

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

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
});
}

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

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

// 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

// 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

  • ✅ 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()

Fonctionnalité@Input() (Ancien)input() (Moderne)
TypePropriétéSignal
Accèsthis.namethis.name()
RéactivitéManuelle (ngOnChanges)Automatique
Requis@Input({ required: true })input.required<T>()
Défaut@Input() x = 5input<number>(5)
Transform@Input({ transform })input(0, { transform })
Fonctionne avecDétection de changementsSignals, computed(), effect()

output() vs @Output()

Fonctionnalité@Output() (Ancien)output() (Moderne)
ImportEventEmitterAucun nécessaire
Déclarationnew EventEmitter<T>()output<T>()
Émettre.emit(value).emit(value)
Sûreté des typesManuelleAutomatique