Transférez les données avec les input

Après avoir découvert les Signals en isolation, vous allez maintenant découvrir la puissance de la communication entre components avec trois types d'objet : les input, les output, et les model :

  • les input permettent de créer des attributs personnalisés sur des components — comme les @Input()— avec la particularité d'en faire des Signal

  • les output créent des événements personnalisés pouvant être émis par vos components ; ce ne sont pas strictement des Signal, mais ils font partie du même univers de réactivité

  • les model sont des input spéciaux qui font remonter les changements de valeur aux parents — ils permettent le two-way binding, un peu comme les  NgModel

Dans ce chapitre, vous allez utiliser les input pour transmettre les données des tâches de l'application.

Transmettez aux enfants

Dans l'application Signalize, le component parent de toutes les cartes de tâche estTaskGridComponent. Il reçoit un Signal depuisTaskServicequi contient et émet le tableau des Tasks à chaque modification, mais pour l'instant la grille n'interagit pas du tout avec ses enfants.

Du coup, je vous propose de commencer par utiliser desinput(la version Signal des@Input()) pour mettre en place cette transmission.

Commencez par modifier TaskCardComponent pour y ajouter uninputde typeTask:

export class TaskCardComponent {
  task = input<Task>();
  completed = false;
}

Cetinputexpose le même fonctionnement qu'un@Input()vis-à-vis le component parent, donc dans le template de TaskGridComponent :

<div class="task-grid">
	@for (task of tasks(); track task.id) {
        <app-task-card [task]="task"/>
    }
</div>

Visuellement, rien n'a encore changé, car TaskCardComponent n'utilise aucune des informations qui émanent de ce Signal. Changeons cela tout de suite, en y modifiant la propriétécompletedpour qu'elle dérive detask  :

export class TaskCardComponent {
  task = input<Task>();
  completed = computed(() => this.task()?.completed ?? false);
}

Alors je ne sais pas vous, mais je n'aime pas du tout ce besoin de "programmation défensive", à devoir traiter les cas oùtaskne serait pas défini. D'ailleurs, une instance de TaskCardComponent n'a pas de sens sansTaskà afficher : rendons donc cetinputobligatoire, et retirons ces?horribles :

export class TaskCardComponent {
  task = input.required<Task>();
  completed = computed(() => this.task().completed);
}

C'est beaucoup mieux comme ça !

Il reste une dernière modification nécessaire : dans le template de TaskCardComponent, il faut changer l'utilisation de completed, car il s'agit maintenant d'un Signal.

<div class="task-card" [class.completed]="completed()">
	<app-task-card-header/>
    <app-subtasks-list/>
</div>

L'état "complété" d'une tâche est la seule information exploitée pour l'instant, et on en voit déjà le résultat dans l'application :

Deux cartes de tâche apparaissent en vert
Tâches complétées

Faisons de même pour TaskCardHeaderComponent.

Créez uninputrequis (vous pouvez enlever les autres propriétés, elles ne serviront plus) :

export class TaskCardHeaderComponent {
  task = input.required<Task>();
}

Remplacez les utilisations des anciennes propriétés par les propriétés duinput:

<div class="task-card-header" [class.completed]="task().completed">
	<h2 class="task-title">{{ task().title }}</h2>
	<div class="task-actions">
		@if (task().completed) {
			<app-task-card-action-button action="cancel"/>
		} @else {
			<app-task-card-action-button action="complete"/>
		}
		<app-task-card-action-button action="delete"/>
	</div>
</div>
<p class="task-description">{{ task().description }}</p>

Il faut ensuite passer laTaskdepuis le parent :

<div class="task-card" [class.completed]="completed()">
    <app-task-card-header [task]="task()"/>
    <app-subtasks-list/>
</div>

Ainsi, dans l'application, ça commence à ressembler à quelque chose :

Les tâches s'affichent avec leurs titres et descriptions respectifs
Des tâches différentes

Il ne reste plus qu'à faire la même chose pour les sous-tâches. 

Pour SubtasksListItemComponent cependant, le  inputest requis :

export class SubtasksListItemComponent {
  subtask = input.required<Subtask>();
}

Pour faciliter cette modification au niveau du template, vous pouvez utiliser@letpour générer des alias qui correspondent aux noms des propriétés actuelles :

@let title = subtask().title;
@let completed = subtask().completed;

<div class="subtask-item">
	<input type="checkbox" [id]="title" class="subtask-checkbox" [checked]="completed" />
	<label [for]="title" class="subtask-label">{{ title }}</label>
</div>

Pour SubtasksListComponent, je vous propose de passer seulement le tableau desSubtask[], car ça suffit pour afficher ce component. Au lieu de le marquer comme requis, je vous propose une alternative : fournir une valeur par défaut. Comme ça, si jamais on ne passe pas de sous-tâches, on considère qu'il y a un tableau vide :

export class SubtasksListComponent {
  subtasks = input<Subtask[]>([]);
}

Comme ça, le template donne :

<div class="subtasks-list">
	@for (subtask of subtasks(); track subtask.title) {
        <app-subtasks-list-item [subtask]="subtask"/>
	}
</div>

Et dans TaskCardComponent, il n'y a plus qu'à passer lesSubtask:

<div class="task-card" [class.completed]="completed()">
	<app-task-card-header [task]="task()"/>
	<app-subtasks-list [subtasks]="task().subtasks"/>
</div>

Maintenant, l'application montre toutes les tâches !

Toutes les tâches apparaissent correctement
Maintenant on peut s'organiser

À vous de jouer

Contexte

La barre de progression n'est pas relié aux vraies données de l'application.

Consigne

ProgressBarComponent doit afficher le vrai pourcentage de complétion des tâches. Utilisez un  input  pour l'injecter depuis le component parent.

En résumé

  • input()  génère un Signal à partir d'une propriété passée à un component par son parent

  • input.required()  génère un input requis

  • input()prend un paramètre de type, ex.  input<string>()

On voit bien toutes les tâches, mais on ne peut pas encore interagir avec — ce sera le sujet du prochain chapitre !

Et si vous obteniez un diplôme OpenClassrooms ?
  • Formations jusqu’à 100 % financées
  • Date de début flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous