DTeK Learn logo

Communication entre des composants via @Input et @output : Usages et bonnes pratiques

27/07/2023

Ce qui est bien avec Angular, c’est qu’il te facilite la vie pour créer des composants réutilisables et bien structurés. Intéressons-nous à @Input et @Output, qui permettent la communication entre des composants parents et enfants.

jeune développeur en reconversion professionnelle sur son ordinateur à son bureau

1. @Input – Passer des données du parent à l’enfant

Le truc sympa avec le décorateur @Input, c’est qu’il te permet de créer une sorte de pont à sens unique entre les données d’un composant parent et celles d’un composant enfant. C’est vraiment utile pour passer des données de l’un à l’autre, et ça garantit que ton flux de données reste clair et facile à comprendre.

Étape 1 : Crée le composant enfant

Pour créer un composant enfant, utilise la commande Angular CLI suivante:

ng generate component enfant.

Cette commande générera un nouveau composant nommé EnfantComponent avec les fichiers correspondants.

Étape 2 : Importe Input et crée la propriété @Input

Dans le fichier du composant enfant (enfant.component.ts), importe le décorateur Input depuis le module @angular/core. Ensuite, utilise le décorateur @Input() pour décorer la propriété que tu veux récupérer depuis le composant parent. Par exemple, si tu veux recevoir un objet qui représente un utilisateur :

import { Component, Input } from ‘@angular/core’;

@Component({
    selector: ‘app-enfant’,
    templateUrl: ‘./enfant.component.html’,
    styleUrls: [‘./enfant.component.scss’]
})

export class EnfantComponent {
    @Input() utilisateur: { nom: string, age: number };
}

Dans cet exemple, tu attends un objet utilisateur qui a deux propriétés : le nom et l’âge.

Étape 3 : Déclare la propriété à transmettre dans le composant parent

Dans le composant parent, déclare la propriété que tu veux envoyer au composant enfant. Par exemple, dans le fichier TypeScript du composant parent, ça ressemblerait à ça :

<!— parent.component.ts
déclare un objet utilisateur : —->

export class ParentComponent {
    parentUtilisateur = { nom: ‘Alice’, age: 30 };
}

Étape 4 : Envoie les données depuis le composant parent

Dans le fichier HTML du composant parent (parent.component.html), lie la propriété que tu veux passer au composant enfant en utilisant la syntaxe de liaison de propriété.

<!– Fichier HTML du composant parent –>

<app-enfant [utilisateur]= »parentUtilisateur »></app-enfant>

Dans l’exemple ci-dessus, la propriété parent Utilisateur du composant parent est passée au composant enfant comme propriété utilisateur.

Étape 5 : Utilise les données reçues dans le composant enfant

Maintenant que le composant enfant a bien reçu les données, tu peux les utiliser dans le fichier HTML du composant enfant en utilisant la syntaxe d’interpolation ou dans le fichier TypeScript pour la logique du composant.

<!– Fichier HTML du composant enfant –>

<p>Nom de l’utilisateur : {{ utilisateur.nom }}</p>
<p>Age de l’utilisateur : {{ utilisateur.age }}</p>

Étape 6 : Utilise OnChanges pour détecter les modifications

Si tu as besoin de voir quand la propriété @Input change, implémente l’interface OnChanges et la méthode ngOnChanges() dans le composant enfant. Importe OnChanges et SimpleChanges depuis @angular/core.

import { Component, Input, OnChanges, SimpleChanges } from ‘@angular/core’;
@Component({
    selector: ‘app-enfant’,
    templateUrl: ‘./enfant.component.html’,
    styleUrls: [‘./enfant.component.scss’]
})

export class EnfantComponent implements OnChanges {
    @Input() utilisateur: { nom: string, age: number };
    ngOnChanges(changes: SimpleChanges) {
        if (changes.utilisateur) {
            console.log(‘Changement détecté :’, changes.utilisateur);
            // Tu peux effectuer des opérations supplémentaires en réponse aux changements ici.
        }
    }
}

Dans cet exemple, la méthode ngOnChanges() est appelée chaque fois que la propriété utilisateur change. L’objet changes contient des informations sur les modifications apportées à chaque propriété @Input.

Étape 7 : Ajoute des alias pour les propriétés @Input

Si tu veux donner un autre nom à la propriété @Input dans le composant enfant, tu peux utiliser un alias. Par exemple, si tu veux que la propriété soit appelée data dans le composant enfant, tu peux faire comme ça :

@Input(‘utilisateur’) data: { nom: string, age: number };

Maintenant, la propriété utilisateur du composant parent sera liée à la propriété data du composant enfant.

Étape 8 : Utilise des interfaces pour définir les types de données

Pour que ton code soit plus lisible et plus facile à entretenir, je te conseille d’utiliser des interfaces pour définir les types de données que tu passes avec @Input. Par exemple, tu peux créer une interface Utilisateur comme suit :

// utilisateur.interface.ts
export interface Utilisateur {
    nom: string;
    age: number;
}

// Puis, importe et utilise cette interface dans le composant enfant:

import { Utilisateur } from ‘../interfaces/utilisateur.interface’;
export class EnfantComponent implements OnChanges {
    @Input() utilisateur: Utilisateur;
}

En suivant ces étapes détaillées, tu pourras facilement passer des données du composant parent au composant enfant en utilisant le décorateur @Input, tout en assurant un code clair et maintenable.

2. @Output – Renvoyer des données de l’enfant au parent

Le décorateur @Output est génial ! Il te permet de créer un lien à sens unique pour envoyer des données du composant enfant vers le composant parent en utilisant des événements personnalisés. C’est vraiment pratique pour transmettre des informations entre les composants tout en maintenant un flux de données bien clair et facile à comprendre.

Étape 1 : Importe Output et EventEmitter

Dans le fichier du composant enfant (enfant.component.ts), importe les décorateurs Output et EventEmitter depuis le module @angular/core:

import { Component, Output, EventEmitter } from ‘@angular/core’;

Étape 2 : Utilise le décorateur @Output et crée un EventEmitter

Décore la propriété que tu souhaites envoyer au composant parent avec le décorateur @Output() et instancie un nouvel EventEmitter. Par exemple, si tu souhaites envoyer un événement contenant un message :

@Component({…})
export class EnfantComponent {
    @Output() messageEnvoye = new EventEmitter<string>();
}

Dans cet exemple, nous avons créé un EventEmitter de type string appelé messageEnvoye.

Étape 3 : Émets un événement avec la méthode emit()

Lorsque tu souhaites envoyer des données au composant parent, utilise la méthode emit() pour déclencher l’événement. Par exemple, si tu as un bouton qui envoie un message au composant parent :

envoyerMessage() {
    this.messageEnvoye.emit(‘Bonjour, parent !’);
}

Étape 4 : Écoute l’événement dans le composant parent

Dans le fichier HTML du composant parent (parent.component.html), utilise la syntaxe de liaison d’événement pour écouter l’événement personnalisé émis par le composant enfant.

<!– Fichier HTML du composant parent –>

<app-enfant (messageEnvoye)= »recevoirMessage($event) »></app-enfant>

Dans l’exemple ci-dessus, tu écoutes l’événement messageEnvoye et appelle la méthode recevoirMessage() dans le composant parent lorsque l’événement est déclenché.

Étape 5 : Traite les données reçues dans le composant parent

Crée une méthode dans le fichier TypeScript du composant parent (parent.component.ts) pour traiter les données reçues lors de la réception de l’événement.

export class ParentComponent {
    recevoirMessage(message: string) {
        console.log(‘Message reçu du composant enfant :’, message);
    }
}

Dans cet exemple, la méthode recevoirMessage() affiche le message reçu dans la console.

Étape 6 : Utilise des interfaces pour définir les types de données

Tout comme avec @Input, il est recommandé d’utiliser des interfaces pour définir les types de données émises via @Output. Par exemple, si tu envoies un objet Utilisateur défini précédemment :

import { Utilisateur } from ‘../interfaces/utilisateur.interface’;
export class EnfantComponent {
    @Output()

Étape 7 : Détecte et gère plusieurs événements

Dans certains cas, tu pourras avoir besoin de gérer plusieurs événements émis par le composant enfant. Par exemple, tu peux créer deux EventEmitters pour différents types d’événements :

Exemple de typescript:

@Output() ajoutUtilisateur = new EventEmitter<Utilisateur>();
@Output() suppressionUtilisateur = new EventEmitter<Utilisateur>();

Dans le composant parent, tu peux écouter ces événements séparément :
Exemple de html:

<app-enfant
(ajoutUtilisateur)= »gererAjoutUtilisateur($event) »
(suppressionUtilisateur)= »gererSuppressionUtilisateur($event) »
></app-enfant>

Puis, défini les méthodes correspondantes dans le fichier TypeScript du composant parent (parent.component.ts) pour traiter les données reçues lors de la réception des événements.

Étape 8 : Utilise des services pour une communication complexe

Effectivement, lorsque la communication entre les composants devient plus complexe et difficile à gérer avec @Input et @Output, il est judicieux d’envisager l’utilisation de services Angular pour partager des données et des événements entre les composants. Les services offrent une approche plus flexible et maintenable pour gérer la communication entre les composants.

En suivant les étapes détaillées que nous avons couvertes précédemment, tu seras en mesure d’envoyer facilement des données du composant enfant vers le composant parent en utilisant le décorateur @Output. Cela t’aidera à maintenir un code clair et facile à gérer. Cependant, si les besoins en communication deviennent plus complexes, je te recommande vivement d’explorer l’utilisation des services Angular.

Les services permettent de centraliser la logique de communication et de partager des données et des événements entre les composants de manière plus souple. Cela améliore la maintenabilité de ton code et te donne une plus grande flexibilité dans la gestion de la communication entre les composants.

3. Bonnes pratiques

Utiliser correctement @Input et @Output est essentiel pour garantir un code clair, maintenable et évolutif.

1. Utilise des interfaces pour définir les types de données

Il est recommandé d’utiliser des interfaces pour définir les types de données passées via @Input et émises via @Output. Cela améliore la lisibilité, la maintenabilité et facilite la détection d’erreurs lors du développement.

// utilisateur.interface.ts

export interface Utilisateur {
    nom: string;
    age: number;
}

2. Utilise des alias pour les propriétés @Input et @Output

Dans certains cas, tu souhaites peut-être donner un nom différent à la propriété @Input ou @Output dans le composant enfant. Tu pourras utiliser des alias pour éviter les conflits de noms et rendre le code plus lisible.

// Composant enfant

@Input(‘parentData’) enfantData: Utilisateur;
@Output(‘enfantEvent’) parentEvent = new EventEmitter<string>();

3. Implémente l’interface OnChanges pour détecter les modifications

Lorsque tu dois détecter et réagir aux modifications des propriétés @Input, implémente l’interface OnChanges et la méthode ngOnChanges() dans le composant enfant. Cela te permet de suivre les changements et d’effectuer des actions supplémentaires en réponse.

import { Component, Input, OnChanges, SimpleChanges } from ‘@angular/core’;
@Component({…})
export class EnfantComponent implements OnChanges {
    @Input() utilisateur: Utilisateur;
    ngOnChanges(changes: SimpleChanges) {
        if (changes.utilisateur) {
            console.log(‘Changement détecté :’, changes.utilisateur);
        }
    }
}

4. Utilise des services

Lorsque la communication entre les composants devient complexe, il est préférable d’utiliser des services Angular pour partager des données et des événements entre les composants. Les services offrent une approche plus flexible et maintenable pour gérer la communication entre les composants.

// shared.service.ts

import { Injectable } from ‘@angular/core’;
import { BehaviorSubject } from ‘rxjs’;
@Injectable()
export class SharedService {
    private messageSource = new BehaviorSubject<string>(‘message initial’);
    currentMessage = this.messageSource.asObservable();
    changeMessage(message: string) {
        this.messageSource.next(message);
    }
}

5. Évite une communication bidirectionnelle excessive

Il est important de maintenir un flux de données unidirectionnel pour éviter la complexité et les erreurs potentielles. Si tu trouves que tu as besoin de trop de communication bidirectionnelle entre les composants parent et enfant, envisage de revoir ton architecture et d’utiliser des services pour simplifier la communication.

6. Utilise la syntaxe de liaison de propriétés et d’événements

Assure-toi d’utiliser la syntaxe de liaison de propriétés et d’événements correcte pour les propriétés @Input et @Output. Cela garantit que ton code suit les meilleures pratiques Angular et est facile à comprendre.

Exemple pour @Input :

<!– Fichier HTML du composant parent –>

<app-enfant [utilisateur]= »parentUtilisateur »></app-enfant>

Exemple pour @Output :

<!– Fichier HTML du composant parent –>

<app-enfant (messageEnvoye)= »recevoirMessage($event) »></app-enfant>

7. Utilise des noms de variables et d’événements clairs

Assure-toi que les noms des variables et des événements sont clairs et reflètent leur fonction. Cela permet aux autres développeurs de comprendre rapidement le code et facilite la maintenance du code.

// Composant enfant

@Input() utilisateur: Utilisateur;
@Output() utilisateurSelectionne = new EventEmitter<Utilisateur>();

8. Sépare les responsabilités entre les composants

Chaque composant doit avoir une responsabilité unique et claire. Essaye de ne pas surcharger un composant avec plusieurs responsabilités, et préfère créer des composants plus petits et réutilisables. Cela améliore la maintenabilité et la compréhension du code.

9. Utilise des modificateurs d’accès appropriés

Utilise des modificateurs d’accès appropriés pour les propriétés @Input et @Output. En général, les propriétés @Input et @Output doivent être déclarées comme public pour qu’elles soient accessibles depuis le composant parent.

10. Teste les liaisons @Input et @Output

Absolument ! Il est essentiel de tester les liaisons @Input et @Output pour s’assurer que la communication entre les composants fonctionne correctement. Pour cela, tu peux utiliser des frameworks de test comme Jasmine et des outils de test Angular tels que TestBed.

En écrivant et en exécutant des tests unitaires pour tes composants, tu peux vérifier si les données sont correctement transmises via les liaisons @Input et si les événements sont correctement émis via les liaisons @Output. Cela te permet de garantir que ton code Angular est clair, maintenable et évolutif.

Les tests unitaires te donnent la confiance nécessaire pour valider le bon fonctionnement de ton code et détecter d’éventuels problèmes de communication entre les composants. Cela contribue à la qualité globale de ton application Angular.

Veille à suivre ces bonnes pratiques et à effectuer des tests approfondis pour tes liaisons @Input et @Output. Cela t’aidera à maintenir un code solide et fiable, tout en exploitant efficacement les décorateurs @Input et @Output pour la communication entre les composants.

QCM

Testez vos connaissances sur ce sujet

TL;DR

Voici les principales notions abordées dans cet article :

  1. Comment utiliser le décorateur @Input pour passer des données du composant parent au composant enfant.
  2. Comment utiliser le décorateur @Output pour renvoyer des données et des événements du composant enfant au composant parent.
  3. Les bonnes pratiques pour utiliser les décorateurs @Input et @Output.
  4. Comment intégrer Angular avec Visual Studio Code pour un développement optimisé et productif.

En mettant en pratique ces concepts et en suivant ces bonnes pratiques, tu seras en mesure de créer des applications Angular performantes, maintenables et évolutives avec une communication efficace entre les composants.