Voici la leçon 11 du Cours d’Angular Moderne . Dans la leçon précédente, nous avons rendu une liste de produits en utilisant des signaux et `$(‘product’)` @for. Chaque carte affichait encore du contenu fictif. Il est maintenant temps de connecter les données réelles du composant parent au composant enfant en utilisant input()l’API moderne de signaux d’Angular.
Cet article fait partie de la série du Cours d’Angular Moderne . Consultez la page du cours pour voir la liste complète des épisodes.
Dans cet article, nous aborderons :
- Le schéma de communication entre le parent et l’enfant
- La création d’une entrée obligatoire avec
input.required<T>() - Le rendu des données d’entrée dans le modèle enfant.
- Transmettre les données du parent vers l’enfant via la liaison de propriété
- L’utilisation d’entrées optionnelles avec des valeurs par défaut.
- Les pièges courants à éviter
Le Modèle Mental
Avant d’écrire le moindre code, alignons le modèle :
- Le composant parent est le propriétaire des données (la liste de produits).
- Le processus parent parcourt les produits avec
@for - Le parent transmet un produit à chaque carte enfant.
- L’enfant reçoit les données
input()et les rend.
Ceci est la communication classique entre parents et enfants, mais en utilisant des entrées modernes basées sur les signaux. C’est la première forme de communication entre composants que nous abordons dans ce cours.
Création d’une entrée obligatoire
Ouvrez product-card.ts et ajoutez une entrée obligatoire pour le produit :
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { Product } from '../product';
@Component({
selector: 'app-product-card',
imports: [MatCardModule, MatButtonModule],
templateUrl: './product-card.html',
styleUrl: './product-card.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCard {
readonly product = input.required<Product>();
}
La phrase-clé :
readonly product = input.required<Product>();
Et voici ce que cela signifie :
input.requiredmarque cette entrée comme obligatoire<Product>nous donne une typage strict- L’entrée est basée sur un signal, donc on la lit avec
product()
Si le composant parent oublie de passer ce paramètre, Angular nous avertira lors de la compilation et lors de l’exécution — exactement ce que nous voulons pour des données critiques.
Le décorateur @Input() Hérité
Si vous analysez des codes sources ou des tutoriels plus anciens d’Angular, vous trouvez fréquemment des entrées écrites sous cette forme :
import { Component, Input } from '@angular/core';
import { Product } from '../product';
export class ProductCard {
@Input({ required: true }) product!: Product;
}
C’est l’approche basée sur les décorateurs qui était la norme avant Angular v17.
Les principales différences par rapport à l’API moderne input() :
@Input()est un décorateur —input()est une fonction qui retourne un signal- Avec
@Input()‘__init__’, la valeur est une propriété de classe simple ; avec `__init__`input(), c’est un signal que vous lisez avec `__init__`.product() @Input({ required: true })exige une assertion non nulle (!) —input.required()gère cela proprement, sans syntaxe supplémentaire.@Input()ne s’intègre pas au système réactif des signaux —input()fonctionne naturellement aveccomputed()eteffect()
Les deux syntaxes fonctionnent actuellement dans Angular, mais la syntaxe basée sur les signaux @Input()est obsolète et l’équipe Angular indique son retrait potentiel dans la version 22. La syntaxe basée sur les signaux input()est l’approche recommandée pour tout nouveau code, et c’est celle que nous utiliserons tout au long de ce cours.
Rendu des données d’entrée dans le modèle
Maintenant, remplaçons le contenu simulé du modèle de la carte par des données réelles.
Fichier :product-card.html
<mat-card class="product-card">
<mat-card-header>
<mat-card-title>{{ product().name }}</mat-card-title>
<mat-card-subtitle>Product</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>{{ product().description }}</p>
<p class="price">{{ product().price | currency }}</p>
</mat-card-content>
<mat-card-actions>
<button matButton>Add to Cart</button>
</mat-card-actions>
</mat-card>
Notez que nous appelons `product()` dans le modèle. Cela se produit parce que `input()` retourne un signal d’entrée, et les signaux se lisent avec des parenthèses — la même règle que nous avons utilisée depuis la leçon 5.
Transfert des données du parent vers l’enfant
À présent, nous connectons le modèle parent à l’entrée de l’enfant.
Fichier :products-grid.html
<div class="products-grid">
@for (product of products(); track product.id) {
<app-product-card [product]="product"></app-product-card>
} @empty {
<div class="empty-state">
<h3>No products available</h3>
<p>Check back later for new arrivals!</p>
</div>
}
</div>
Le maillon qui relie tout :
[product]="product"
Le côté gauche représente le nom du champ d’entrée du composant enfant. Le côté droit représente la variable de boucle du composant parent. Désormais, chaque instance de ProductCard reçoit un objet produit de la liste.
Entrées optionnelles avec valeurs par défaut
Les champs obligatoires conviennent parfaitement pour des données essentielles, comme [exemple de données] product. Mais parfois, nous voulons un comportement configurable qui peut recourir à une valeur par défaut.
Nous allons ajouter un champ d’entrée optionnel pour contrôler le texte de l’étiquette du bouton.
Fichier :product-card.ts
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { Product } from '../product';
@Component({
selector: 'app-product-card',
imports: [MatCardModule, MatButtonModule],
templateUrl: './product-card.html',
styleUrl: './product-card.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProductCard {
readonly product = input.required<Product>();
readonly addButtonLabel = input('Add to Cart');
}
Ici, input('Add to Cart') signifie :
- Ce champ est optionnel.
- La valeur par défaut est
'Add to Cart' - Le parent peut la remplacer lorsque c’est nécessaire.
Maintenant, mettons à jour le modèle pour l’utiliser :
Fichier :product-card.html
<mat-card class="product-card">
<mat-card-header>
<mat-card-title>{{ product().name }}</mat-card-title>
<mat-card-subtitle>Product</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>{{ product().description }}</p>
<p class="price">{{ product().price | currency }}</p>
</mat-card-content>
<mat-card-actions>
<button matButton>{{ addButtonLabel() }}</button>
</mat-card-actions>
</mat-card>
Et si vous souhaitez remplacer le texte du bouton par celui du composant parent :
<app-product-card [product]="product" [addButtonLabel]="'View Details'"></app-product-card>
Cela rend le composant encore plus réutilisable sans complexité supplémentaire.
Pièges courants
Deux erreurs typiques à éviter :
- Oublier les parenthèses dans le modèle — écrire
product.nameau lieu deproduct().name. Comme les entrées sont des signaux, il faut les appeler avec()pour lire la valeur. - Marquer des données cruciales comme optionnelles alors qu’elles devraient être obligatoires — si le composant ne fonctionne pas sans une donnée, utilisez
input.required<T>(). Si c’est une configuration avec une valeur par défaut adéquate, utilisezinput(defaultValue).
Une bonne règle :
- Si le composant ne fonctionne pas sans lui →
input.required<T>() - Si c’est une configuration →
input(defaultValue)
Code source
Le code source complet du cours est disponible sur GitHub : loiane/modern-angular .
Prochaine étape
Dans la prochaine leçon, nous approfondirons ce concept en ajoutant une interface utilisateur conditionnelle sur la carte, en utilisant @if et @else pour afficher des prix promotionnels et des étiquettes.
Regardez la vidéo
Cet article fait partie de la série du Cours d’Angular Moderne . Consultez la page du cours pour voir la liste complète des épisodes.




