Contrôle de flux et directives
Introduction
Après avoir exploré les bases des composants, il est temps de plonger dans un autre aspect de la manipulation de l’interface via deux ressources : le contrôle de flux et les directives.
Le mécanisme de contrôle de flux offre une syntaxe moderne pour appliquer des conditions, boucles et autres manipulations dynamiques du DOM de manière simple et optimisée par rapport à des alternatives plus manuelles. Les directives, quant à elles, sont des classes TypeScript. Aussi, pour modifier le DOM, il en existe deux principaux types : les directives d’attribut modifiant l’apparence ou le comportement d’un élément, puis les directives structurelles pour impacter la structure du DOM en ajoutant ou supprimant des éléments.
Ce chapitre vous guidera à travers ces concepts et vous permettra de maîtriser l’utilisation des directives afin d’enrichir vos applications.
Introduction à la micro-syntaxe
Le contrôle de flux ainsi que les directives structurelles utilisent ce que l’on appelle la micro-syntaxe, qui est une sorte de mini-langage dédié, appelé Domain-Specific Language qui va être interprété par Angular, afin de créer et manipuler des templates avec tous les bindings nécessaires.
Points de vocabulaire :
-
let : définit une variable accessible...
Contrôle de flux
1. @if
Le bloc @if permet d’afficher du contenu, en fonction d’une condition, accompagné d’une instruction @else optionnelle :
@if(user.isLoggedIn===true){
<span>Hello User</span>
} @else if (user.isGuest === true){
<span>Hello guest</span>
} @else {
<button>Click here to log in</button>
}
2. @switch
Le bloc @switch, similaire à une instruction switch en JavaScript, permet de définir un ensemble de cas à vérifier. Le premier étant valide va exécuter un rendu.
@switch (user.role) {
@case ('Admin') {
<span>Hello Admin</span>
}
@case ('User') {
<span>Hello User</span>
}
@default {
<span>Hello Guest</span>
}
}
3. @for
Accompagné d’un bloc secondaire, @empty permet de créer une boucle dans le rendu :
<ul>
@for(item of items; let i = $index; track item.id){
<li>{{ i + 1 }}. {{ item.name }}</li>
@empty {
<li>No item</li>
}
</ul>
Il y a plusieurs choses à noter : des variables sont fournies par le framework, de la même manière que pour un binding d’événement, et préfixées par un symbole $. L’instruction track est obligatoire, car elle permet à Angular de garder une correspondance entre les éléments du rendu et les données, afin d’optimiser toute modification de l’interface. Elle doit cibler une propriété unique ; en l’occurrence ici l’identifiant de notre donnée. Enfin, nous avons aussi la présence du bloc secondaire, afin de définir un résultat en cas de collection vide.
Parfois, il est difficile de trouver une valeur unique à chaque élément d’un tableau. Dans ces cas-là, on peut utiliser la valeur i représentant $index, ou bien, si vos éléments sont...
Les directives
1. Directives d’attribut
a. ngClass
La directive ngClass permet de changer l’attribut class d’un élément ; le but étant de pouvoir dynamiquement assigner des classes CSS à vos éléments. Vous pouvez fournir les valeurs de plusieurs façons :
-
via une chaîne de caractères avec les classes CSS que vous souhaitez voir séparées par des espaces :
<span [ngClass]="'red big'"> Angular 18 </span>
-
via un tableau de chaîne de caractères :
<span [ngClass]="['red','big']"> Angular 18 </span>
-
via un objet JavaScript :
<span [ngClass]="{red:true, 'clickable navigation': isLink}">
Angular 18 </span>
Attention pour ce dernier exemple : chaque classe est une propriété et la valeur doit être un booléen.
b. ngStyle
Cette directive est proche de la précédente, car elle permet de définir du style CSS directement dans votre template, par exemple :
<span [ngStyle]="{textDecoration:'underline'}">Angular 18</span>
c. ngModel
La directive ngModel est spécifique aux formulaires, et donc aux différents éléments HTML (HyperText Markup Language) comme les Inputs. D’une manière générale, on peut définir une valeur initiale à un Input en ajoutant l’attribut "value" avec une valeur par défaut. Donc nous pouvons, par exemple, avoir une variable "name" qui soit assignée à un Input via l’attribut "value". Cependant, même si l’utilisateur entre du texte dans le champ input, la variable "name" restera inchangée, car l’attribut "input" lie la donnée (ou bind) seulement dans un seul sens, et donc n’écoute pas les modifications utilisateur.
Grâce à ngModel, nous allons pouvoir utiliser une liaison à double sens (ou binding two way), ce qui permet d’afficher dans le HTML le contenu d’une variable, mais aussi de mettre à jour nos variables depuis les entrées utilisateur :
<input type="text" [(ngModel)]="name"/>
Cette directive sera expliquée plus...
Projet fil rouge
Au chapitre Les composants, nous avions pu créer et afficher des composants ; cependant, seuls la première colonne et le premier ticket étaient affichés. Dans cette étape du projet Kanban, nous allons transformer notre interface statique en dynamique en utilisant l’expression @for, puis, dans un deuxième temps, créer une directive pour pouvoir déplacer les tickets via un glisser-déposer afin de les transporter de colonne en colonne ou bien de les réordonner.
1. Tableau de correspondances
Étape préliminaire importante : il nous faut regrouper les tickets par colonnes tout en conservant leur ordre au sein de la colonne. Nous allons donc ajouter une méthode dans la page principale afin de faire ce calcul.
private getTicketsMap (columns: Column[], tickets: Ticket[]) {
const draftMap: Record<string, Ticket[]> = {};
for (let ticket of tickets) {
if (!draftMap[ticket.columnId]) {
draftMap[ticket.columnId] = [];
}
const columnId = ticket.columnId;
draftMap[columnId].push(ticket);
}
for (let { id } of columns) {
const currentColumn = draftMap[id];
if (!currentColumn) {
draftMap[id] = [];
} else currentColumn.sort((a, b) => a.order - b.order);
}
return draftMap;
}
Puis vous pouvez supprimer la propriété publique tickets pour la remplacer par :
ticketByColumnId: Record<string, Ticket[]> = {};
Enfin, dans le constructeur du composant, nous allons appeler notre nouvelle méthode :
this.ticketByColumnId = this.getTicketsMap(
allBoard.columns,
allBoard.tickets
);
2. Rendre le template dynamique
Nous pouvons donc commencer directement depuis la page BoardPage et envelopper, dans le template, l’utilisation du composant <app-column/> par un bloc @for. La même opération doit être faite dans le composant de colonne afin...
Conclusion
Dans ce chapitre, nous avons approfondi nos connaissances grâce aux directives, d’attribut et structurelles, tout en découvrant les nouveautés apportées par le contrôle de flux offrant une syntaxe modernisée et plus expressive pour manipuler dynamiquement le contenu des templates.
Dans la section pratique, nous avons transformé notre interface Kanban en utilisant des contrôles de flux pour boucler sur les éléments à afficher et des directives Angular pour la rendre plus interactive. La fonctionnalité de glisser-déposer illustre bien comment manipuler des éléments de l’interface de manière dynamique. Pour implémenter ce mécanisme, nous avons découvert de nouveaux décorateurs, utiles tant pour intercepter des événements natifs que pour accéder aux directives enfants, créant ainsi une structure solide, pour un comportement complexe. Structurer plusieurs directives avec une hiérarchie comme cela, utilisant @ContentChildren, est une pratique courante qui permet des interactions simples, sans surcharger les composants. Cela rend le comportement réutilisable et non spécifique, pour une bonne séparation des responsabilités.
Le code final de cet exercice est également disponible sur GitHub à l’URL (Uniform Resource Locator) suivante :...