Archives Wild Code School
Angular

 07 - Communication entre les composants : @Input

Quête 7 sur 16 · 2 images archivées

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 parent vers son (ou ses) composants enfants via @Input().

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

Objectifs

✅ Comprendre @Input() ✅ Utiliser @Input()

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 (celle qui nous intéresse dans cette quête)
  • @Output() : communication enfant → parent (que tu verras dans la quête suivante)
  • @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 @Input().

Mais alors @Input(), c'est quoi ?

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

Info

Tu l'as donc compris, pour transmettre des données entre composants, il faut au moins deux composants.

Tu as déjà AppComponent (qui sera le parent). On va créer un ChildComponent (qui sera donc l'enfant).

Info

Dans angular, un composant A est parent de B quand A appelle le selector de B dans HTML.

Subconséquemment, ChildComponent est l'enfant de AppComponent (et Child component pourrait lui-même avoir des enfants).

C'est dans l'enfant que l'on écrit le décorateur @Input(). Voici le composant enfant child.component.ts :

typescript
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss']
})
export class ChildComponent {

// On initialise notre propriété avec une valeur par défaut
// On décore notre propriété avec @Input()
 @Input() myInputData: string = "";

}

Comme tu peux le voir, c'est plutôt facile. Il suffit de décorer ta propriété avec @Input().

La propriété décorée avec @Input() en Typescript côté enfant sera transformée en attribut du côté HTML parent :

html
<app-child [myInputData]=" 'chaine transmise via @Input' "></app-child>

Il ne faut surtout pas oublier les [ ] autour de l'attribut, sinon il n'y aura pas de binding de propriété entre ce que tu as défini en attribut HTML et ta propriété côté Typescript.

Ce que tu dois retenir :

  • @Input() est à utiliser lorsqu'un composant enfant doit recevoir des données en provenance de son parent
  • @Input() s'utilise côté typescript (enfant) et on utilise [ ] (côté HTML parent) pour le binding de propriété
  • Tout type de données peut être transmis : string, number, Object, Array, etc... : il n'y a pas de restriction. Sacrément pratique 🤓
  • Un composant peut avoir plusieurs propriétés @Input() déclarées

↗
Exemple @Input()
angular.io
↗
API @Input()
angular.io
↗
Input Video
youtube.com

Exemple : des cookies dans la cuisine 🍪

Aujourd'hui, nous allons cuisiner des cookies !
Imaginons un composant qui représente une cuisine <app-kitchen> (composant parent), où nous allons sélectionner la quantité de chaque ingrédient et le nombre de cookies à cuire. Les quantités sont renseignées à l'aide d'un formulaire et sont stockées dans des variables (ex : flourQuantity pour la farine, eggQuantity pour les œufs, etc...) :

html
// in app-kitchen
<form (ngSubmit)="startCooking()">

  <h4>My Kitchen</h4>
  <label for="flour">Flour quantity :</label>
  <input id="flour" type="number" name="flour" [(ngModel)]="flourQuantity">

  <label for="salt">Salt quantity :</label>
  <input id="salt" type="number" name="salt" [(ngModel)]="saltQuantity">

  <label for="sugar">Sugar quantity :</label>
  <input id="sugar" type="number" name="sugar" [(ngModel)]="sugarQuantity">

  <label for="egg">Egg quantity :</label>
  <input id="egg" type="number" name="egg" [(ngModel)]="eggQuantity">

  <label for="number">Number of cookies :</label>
  <input id="number" type="number" name="number" [(ngModel)]="numberOfCookies">

  <div>
    <button type="submit">Cuisiner 🍪</button>
  </div>

</form>

A l'envoi du formulaire (via le bouton submit), nous voulons cuire nos supers cookies. Dans un premier temps, indiquons que nos ingrédients sont prêts à cuire à l'aide d'un boolean :

typescript
// in app-kitchen
startCooking() {
  this.isCooked = true;
}

C'est déjà une bonne première étape. Mais il nous manque de quoi cuire nos bons petits cookies 🍪

Nous allons donc créer un second composant qui sera notre four. Nous allons l'appeler <app-magic-oven> (c'est le composant enfant et oui, c'est un four magique).

C'est sur ce composant que nous allons utiliser @Input(), car nous avons besoin de faire "entrer" les ingrédients dans le four.

image

On déclare donc chaque ingrédient dans notre composant <app-magic-oven> en tant que propriété, sans oublier de les décorer avec @Input() et de les initialiser :

typescript
//in app-magic-oven 
@Input()
startCooking: boolean : false;

@Input()
numberOfCookies: number = 0;

@Input()
flour: number = 0;

@Input()
salt: number = 0;

@Input()
sugar: number = 0;

@Input()
egg: number = 0;

On a donc une cuisine avec des ingrédients et un four prêt à recevoir les ingrédients. Maintenant, nous allons faire la liaison entre les deux via le HTML de <app-kitchen> :

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

→ Nous venons de rajouter notre four dans le template de notre cuisine (partie HTML du parent), et nous avons bindé chaque quantité issue de notre cuisine avec les quantités attendues en entrée par notre four.

Et "Vwala !" 🎉 🎉  Notre cuisine communique maintenant avec notre four magique !

Comme notre four est magique, il nous indique les quantités de chaque ingrédient ainsi que le nombre de cookies réalisés :

html
Magic oven is making {{numberOfCookies}} cookies with :
<ul>
  <li>{{flour}} g of flour</li>
  <li>{{salt}} g of salt</li>
  <li>{{sugar}} g of sugar</li>
  <li>{{egg}} eggs</li>
</ul>

N'oublie jamais :

  • Pas de pierre, pas de construction
  • Pas de construction, pas de palais
  • Pas de palais.... pas de palais
  • Pas d'@Input(), pas de cookie ! 😎

Tu peux retrouver l'exemple complet et cuire tes cookies toi-même à cette adresse : générateur de cookies.

Challenge 💪

C'est une bonne situation ça, développeur ? 🤓

Tu vas créer 2 composants avec une hiérarchie parent/enfant :

  • Le component parent (DeveloperComponent) sera chargé d'afficher les informations d'un développeur : son nom, son prénom, son âge ainsi que sa biographie.
  • Il devra aussi posséder au minimum 2 compétences. Chaque compétence sera affichée dans un autre composant (SkillComponent).

Modèle de données de la classe Developer :

  • lastName de type string
  • firstName de type string
  • age de type number
  • gender de type string
  • bio de type string
  • skills est un tableau de type Skill

Modèle de données de la classe Skill :

  • name de type string
  • logo de type string
  • site de type string

Structure du répertoire src/app :

bash
app
├── ...
├── models
│   ├── developer.model.ts
│   └── skill.model.ts
├── developer
│   ├── developer.component.css
│   ├── developer.component.html
│   ├── developer.component.spec.ts
│   └── developer.component.ts
└── skill
    ├── skill.component.css
    ├── skill.component.html
    ├── skill.component.spec.ts
    └── skill.component.ts

Les dossier models, developer et skill doivent être au même niveau.

Certains répertoires (générés automatiquement par ng) ont volontairement été omis. Les fichiers *.spec.ts ne sont pas obligatoires.

Poste l'url de ta Sandbox comme solution.

Pour réaliser ce challenge, tu devras :

  • Créer une classe Developer (developer.model.ts) représentant le modèle de données d'un développeur.

  • Créer une classe Skill (skill.model.ts) représentant le modèle de données d'une compétence.

  • Générer un composant <app-developer> (DeveloperComponent), qui sera chargé d'afficher les informations globales du développeur (nom, prénom, âge, sexe, biographie).

  • Générer un composant <app-skill> (SkillComponent), qui sera chargé d'afficher une des compétences du développeur (nom de la technologie, logo et lien vers le site officiel).

  • Le composant <app-developer> utilisera la directive *ngFor pour afficher autant de composants <app-skill> qu'il y a de compétences pour le développeur. C'est-à-dire qu'il n'y aura qu'un seul composant <app-skill> déclaré dans le HTML du composant <app-developer>

  • Les deux composants <app-developer> et <app-skill> doivent être au même niveau hiérarchique dans le dossier projet (voir structure du projet).

  • D'un point de vue structure HTML, <app-skill> est un composant enfant de <app-developer> et <app-developer> est un enfant de <app-root>.

  • La compétence qui sera affichée par le composant <app-skill> lui sera transmise via binding [ ] grace au @Input().

Critères de validation ✅

  • La structure du projet est respectée.
  • Les modèles de données Developer et Skill sont respectés.
  • Les noms des composants et leurs propriétés sont respectés.
  • Toutes les variables sont correctement typées et initialisées
  • Les compétences sont transmises de <app-developer> vers <app-skill> en utilisant @Input.

Ressources partagées sur cette quête