Archives Wild Code School
Angular

 08 - Communication entre les composants : @Output

Quête 8 sur 16 · 1 image archivée

title-book
Introduction

Grâce au binding, tu as appris à faire transiter des données au sein du même composant (TS → HTML et HTML → TS).

Cette quête permet de comprendre la transmission de données entre 2 composants. Pour être plus exact, tu vas apprendre à faire transiter des données depuis un composant enfant vers son composant parent via @Output().

Avant d'aller plus loin, assure-toi d'avoir terminé ces quêtes 👇

Objectifs

✅ Comprendre @Output() ✅ Utiliser @Output()

Pourquoi communiquer entre les composants ?

Comme tu le sais maintenant, une application web Angular est un ensemble de composants réutilisables. Ces composants doivent être le plus simple possible et limités à effectuer une tâche définie et parfaitement identifiable.

→ Tu vas donc créer beaucoup de composants et, parfois, tu auras besoin d'envoyer une information depuis un composant vers un autre.

Sache qu'il existe plusieurs manières de communiquer entre 2 composants :

  • @Input() : communication parent → enfant (que tu as sans doute vu dans la quête précédente)
  • @Output() : communication enfant → parent (celle qui nous intéresse dans cette quête)
  • @ViewChild/@ViewChildren : récupération des instances des composants enfants (très pratique selon les contextes)
  • exportAs : récupération d'une instance de component enfant (HTML)

✅ Dans cette quête, nous nous intéresserons uniquement au decorator @Output().

Mais alors @Output() et EventEmitter, c'est quoi ?

@Output() est un Décorateur Angular, c'est-à-dire une instruction qui permet d'enrichir le comportement d'un objet en lui ajoutant des fonctionnalités. C'est en décorant une propriété avec @Output() que tu pourras transmettre des données en sortie du composant.

Regardons comment utiliser @Output() : (childcomponent.ts)

typescript
@Output()
dataChange: EventEmitter<string> = new EventEmitter();

C'est plus ou moins similaire à la déclaration d'@Input(). Cependant, la propriété Décorée avec @Output() doit être du type EventEmitter.

✅ L'EventEmitter est lui-même typé :  le type va dépendre du type de donnée que l'on souhaite faire passer.

En Typescript, lorsque tu souhaites typer un type, tu utilises les chevrons <type> après le type à typer.

C'est une bonne situation ça, typeur ?

En d'autres termes, dans notre exemple, on souhaite faire passer une chaine de caractères, donc un type string : c'est pour ça que l'on écrit EventEmitter<string>.

Info

Tu peux voir l'EventEmitter comme un véhicule qui transporte ta donnée. Tu dois préciser si ce véhicule transporte du string, du number, du boolean, etc...

C'est avec cette classe et sa méthode .emit() que tu pourras émettre/envoyer des données vers le composant parent child-component.ts :

typescript
sendDataToParent(): void {
  this.dataChange.emit('Chaîne de caractères envoyée au parent');
}

Comme pour @Input(), la propriété décorée avec @Output() en TS sera transformée en attribut côté HTML dans le parent-component.html:

html
<app-child-component (dataChange)="onDataChange($event)"></app-child-component>
Info

Mais contrairement à @Input(), il faut déclarer l'attribut en tant que binding d'événement en "l'entourant" de ( ), comme lorsque tu lies une fonction sur l'événement click par exemple.

Nous avons lié l'@Output() dataChange à la fonction onDataChange() déclarée dans le composant parent. Elle prend en paramètre l'objet $event qui contient les données passées par la méthode .emit(). Dans le parent.component.ts :

typescript
onDataChange(event: string): void {
  console.log(event);
  // 'Chaîne de caractères envoyée au parent'
}

Pour résumer :

Le composant enfant déclenche la fonction emit(), qui elle-même va déclencher la fonction onDataChange() dans le composant parent. La liaison entre les deux se fait en HTML, via le binding d'événement de la propriété enfant (décorée @Output()) sur la fonction du composant parent.

Ce que tu dois retenir :

  • @Output() est à utiliser lorsqu'un composant enfant doit communiquer (envoyer des données) vers son composant parent

  • @Output() s'utilise côté TS enfant et on utilise ( ) pour le binding d'événement côté HTML parent

  • La propriété décorée avec @Output() doit être du type EventEmitter<type>

  • La fonction qui récupère l'événement déclenché sur @Output() est déclarée dans le parent

  • Tout type de données peut être transmis : string, number, Object, Array etc... : il n'y a pas de restrictions !

  • Un composant peut avoir plusieurs propriétés @Output() déclarées

↗
Exemple @Output() sur la documentation officielle
angular.io
↗
API @Output()
angular.io
↗
Vidéo sur l'Output()
youtube.com

Output() dans la pratique

Reprenons notre exemple de cuisine, de cookies et de four magique.

Cette fois ci, nous allons sortir nos cookies du four et les stocker dans notre cuisine (sans les manger 😏)

Commençons par ajouter une nouvelle fonctionnalité à notre four <app-magic-oven> : la récupération des cookies.

typescript
// in app-magic-oven

  @Output()
  cookies: EventEmitter<number> = new EventEmitter(); 

  sendCookies() {
    this.cookies.emit(this.numberOfCookies);
  }

Deux nouvelles choses pour notre fonctionnalité :

  • Une propriété cookies de type EventEmitter<number> décorée avec @Output() : c'est cette propriété que l'on utilisera en tant qu'attribut dans le HTML

  • Une fonction sendCookies qui utilise la méthode .emit() pour envoyer nos cookies

Au niveau du HTML de l'enfant, nous attribuons l'événement click sur la fonction sendCookies :

html
<!-- in app-magic-oven -->
<button (click)="sendCookies()">Envoyer des cookies vers du four vers la cuisine</button>

Jusqu'ici, tout va bien ! Maintenant, il faut faire en sorte que notre cuisine (<app-kitchen>) récupère les cookies envoyés par notre four. Nous allons par conséquent créer une nouvelle fonction onGetCookies dans notre cuisine :

typescript
// in app-kitchen
 onGetCookies(event: number): void {
    this.cookiesCooked =+ event;
  }

Cool, mais comment .emit() va t-il déclencher onGetCookies() ? Comme pour @Input(), la liaison se fait en HTML, sauf que pour @Output() on utilise le binding d'événements ( ) :

html
<!-- in app-kitchen -->
<app-magic-oven
(cookies)="onGetCookies($event)"
[startCooking]="cook" 
[numberOfCookies]="numberOfCookies"
[flour]="flourQuantity" 
[salt]="saltQuantity" 
[sugar]="sugarQuantity"
[egg]="eggQuantity">
</app-magic-oven>

Nos attributs représentant nos ingrédients décorés @Input() sont toujours là, mais on retrouve aussi un nouvel attribut cookies qui correspond à notre propriété de type EventEmitter<number>, décorée @Output()

Comme cité plus haut, on utilise le binding d'événements avec ( ) pour lier cookies (de <app-magic-oven>) avec la fonction onGetCookies de <app-kitchen>.

Tu peux retrouver l'exemple complet et récupérer tes cookies toi-même à cette adresse : Générateur de cookies @Output()

Challenge 💪

Onomato-quoi ? 🥸

Pour ce challenge, tu vas créer un composant contenant un formulaire (l'enfant) et un composant parent contenant une liste (et le composant enfant).

Ici tu vas traiter une liste d’onomatopées (cas simple où tu peux représenter les onomatopées par de simples string).

Dans le composant AppComponent :

  • onomatopoeiaList est une propriété de type array de type string
  • onReceiveNewOnomatopia() est une méthode de type void

Dans le composant CreateOnomatopiaComponent:

  • newOnomatopia est une propriété de type string
  • sendOnomatopiaToParent est une propriété de type EventEmitter
  • createOnomatopia() est une méthode de type void

Pour réaliser ce challenge, tu devras :

  • Afficher la liste des onomatopées dans le AppComponent
  • Générer un composant <app-create-onomatopia> (CreateOnomatopiaComponent) qui contiendra un formulaire permettant de créer une onomatopée
  • Déclarer CreateOnomatopiaComponent en tant que composant enfant de AppComponent dans le HTML.
  • À chaque création d'onomatopée, AppComponent affichera le nouvel élément grâce à un transfert d’information effectué via le Output().

Critères de validation ✅

  • Les noms des composants, de leurs propriétés & méthodes sont respectés.
  • La création a lieu dans le CreateOnomatopiaComponent
  • L'affichage a lieu dans le AppComponent
  • Publier la solution sur Sandbox

Ressources partagées sur cette quête