Angular - Les directives : comment les utiliser et pourquoi ?

Dans cet article vous allez découvrir les directives d'Angular, comment les utiliser et dans quel contexte.

Qu’est-ce qu’une directive ?

Pour commencer, une définition : les directives d'Angular sont des classes qui ajoutent un comportement supplémentaire aux éléments du DOM.

Ces directives s'appliqueront dans vos fichiers HTML, sur les balises de composants et/ou balises HTML.

Les différents types de directives

On dit qu'une directive est structurelle lorsqu'elle modifie la structure du DOM, c'est-à-dire lorsqu'on ajoute, supprime ou déplace une balise.

Lorsqu'on dit par contre qu'une directive est une directive d'attribut, on parle d'une directive qui change un attribut de l'élément du DOM, sans modifier la structure du DOM dans son ensemble.

On peut aussi parler de directive native ou intégrée lorsqu'elle est fournie par Angular.

Structural directives

Commençons donc par voir les Structural directives qui permettent de travailler sur la structure de votre HTML. Ces directives sont fournies par Angular et vous permettent d'ajouter, retirer ou déplacer les éléments du DOM.

Elles sont très peu nombreuses donc faciles à retenir. Tant mieux, parce qu'elles sont importante à maitriser.

En voici la liste complète :

  • *NgIf
  • *NgFor
  • [NgSwitch], *NgSwitchCase, *NgSwitchDefault

Exemples d'utilisation

Voyons comment utiliser ces 3 directives :

NgIf

Charge une balise sur la page uniquement si la condition est vraie.

1
<p *ngIf="condition">Affiche ce paragraphe si la condition est vraie.</p>
Chargement conditionnel

NgFor

Permet de dupliquer une balise.

1
2
3
<ol>
  <li *ngFor="let user of users"> {{user.name}} {{user.lastname}} </li>
</ol>
Liste HTML générée par Angular

Dans cet exemple, la balise <li></li> est dupliquée autant de fois qu'il y a de user dans la variable users. Chaque user devient alors accessible depuis la variable définie dans le let du *ngFor

NgSwitch

1
2
3
4
5
<div [ngSwitch]="expression à évaluer">
  <p *ngSwitchCase="valeur_1"> Ce paragraphe est affiché si l’expression évaluée == valeur_1.</p>
  <p *ngSwitchCase="valeur_2"> Affiche ce paragraphe si l’expression à évaluer == valeur_2.</p>
  <p *ngSwitchDefault> Affiche ce paragraphe si aucune des valeurs n'est égale à l'expression.</p>
</div>
Plusieurs affichages différents selon la valeur d'une expression

C'est tout pour les directives structurelles !

Attribute directives

Voyons maintenant les Attributes directives. Ce sont des classes TS ayant comme décorateur “@Directive” permettant d'agir sur les attributs d’éléments du DOM.

Directives d'attributs intégrées

Avec les directives d’attributs intégrées d'Angular, vous pouvez gérer certains éléments de votre DOM tels que les formulaires, les styles et les classes.

On retrouve notamment :

  • NgClass
  • NgStyle
  • NgModel

Exemples d'utilisation

NgClass

On peut ajouter ou supprimer plusieurs classes CSS simultanément avec ngClass. Sur un élément que vous souhaitez styliser ajoutez un ngClass [ngClass] et si l’expression évaluée renvoie true, alors Angular appliquera la classe.

1
2
3
4
<div [ngClass]="{ 'card' : isCard && isFirst,
                  'card bg-blue' : isCard && isSecond,
                  '': false }">
</div>
test.component.html

NgStyle

NgStyle permet de définir plusieurs styles, selon l'état du composant.

Dans votre composant :

1
2
3
4
5
6
7
8
9
10
11
12
currentStyles: Record<string, string> = {};

setCurrentStyles() {
  etatUn: boolean;
  etatDeux: boolean = true;
  etatTrois: boolean;
  this.currentStyles = {
    'font-style': this.etatUn ? 'italic' : 'normal',
    'font-weight': !this.etatDeux ? 'bold' : 'normal',
    'font-size': this.etatTrois ? '24px' : '12px'
  };
}
test-style.component.ts

Puis côté html, on applique les styles à la balise en les passant à la directive ngStyle.

1
2
3
4
<div [ngStyle]="currentStyles">
  Le texte de cette div est initialement en italique, 
  d'épaisseur normale, et de grande taille (24px).
</div>
test-style.component.html

NgModel

La directive NgModel nous permet d'agir sur l'attribut "value" de nos balises

C'est dans l'attribut value qu'on retrouve la valeur saisie par un utilisateur dans une balise input.

NgModel s'utilise donc sur un input pour réaliser une liaison bidirectionnelle entre votre template et votre component.

1
2
<label for="example-ngModel">[(ngModel)]:</label>
<input [(ngModel)]="currentItem.name" id="example-ngModel">
test-ng-model.component.ts

Une variable annotée "ngModel" sera dite "two-way bindée". Donc si l'utilisateur modifie la donnée côté template, elle est aussitôt modifié en temps réel dans le component. Et vice-versa si vous changez la valeur depuis le component, la valeur sera actualisée instantanément sur le template.

NB : Pour utiliser NgModel, vous devez importer FormsModule dans votre module. Doc Angular.

Toutefois, attention car avec cette syntaxe on ne peut définir qu’une seule propriété liée. C’est à dire que la valeur de l’input ne peut pas être liée à 2 attributs de composant ou +.

Pour ce cas précis vous devrez utiliser les reactive forms. Nous les verrons dans un prochain article dédié aux formulaires.

Créer sa propre directive d'attribut

Générer le squelette de la directive

En premier lieu, pour créer une directive, utilisez la commande de la CLI :

1
2
ng generate directive <nom>
ng g d <nom>

Cela aura pour effet de créer les fichiers typescript :

  • src/app/<nom>.directive.ts
  • un fichier de test correspondant : src/app/<nom>.directive.spec.ts

Et également de déclarer la classe directive dans le fichier AppModule.

Voici le contenu généré par la CLI dans le fichier src/app/<nom>.directive.ts :

1
2
3
4
5
6
7
8
import { Directive } from '@angular/core';

@Directive({
  selector: '[app<Nom>]'
})
export class <nom>Directive {
  constructor() { }
}
<nom>.directive.ts

La propriété selector du décorateur spécifie le sélecteur d'attribut de la directive, [app-]. C'est en plaçant ce sélecteur sur une balise de votre template que vous pourrez utiliser votre directive.

Vous pouvez donc désormais écrire la logique votre directive !

Ecrire la logique de votre directive

Voici une directive qui colore en bleu le background de l'élément DOM sur lequel on l'utilise :

1
2
3
4
5
6
7
8
9
10
11
import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[appAnyDirective]'
})
export class AnyDirective {

  constructor(el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'blue';
  }
}
Injection du ElementRef dans any.directive.ts

Vous remarquez que l'on a importé ElementRef depuis @angular/core. ElementRef est un wrapper qui nous permet d’accéder directement à l'élément du DOM.

Donc si vous ajoutez un ElementRef dans le constructor(), Angular vous y injecte une référence à l'élément DOM hôte, l'élément auquel vous appliquez appAnyDirective ! Vous pouvez ensuite manipuler votre objet du DOM via cette référence.

Utiliser votre directive d'attribut

Finalement, pour utiliser la directive, ajoutez comme attribut à la balise le nom de la directive que vous avez précédemment créée.

1
<p appAnyDirective>Arrière plan bleu</p>.
sample.component.ts

Configuration des directives

Le paramètre selector

Le paramètre selector est le plus important car il va indiquer comment on utilisera la directive. En effet, selon la syntaxe qu’on utilisera, la directive sera représentée par un objet du DOM, une classe CSS ou un attribut dans une balise HTML.

Le selector se paramètre dans le décorateur @Directive() :

1
2
3
4
@Directive({
 selector: <valeur>
})
export class AnyDirective {}

Directive utilisée en tant que balise

selector: 'any-directive'

Dans ce cas-ci, la directive doit être utilisée comme élément dans le template :

1
<any-directive></any-directive>

D'ailleurs, saviez-vous que Component est une classe fille de Directive (Component extends Directive) ?

Un Component n'est en fait qu'une directive à laquelle on a associé une vue (template HTML)

Directive utilisée en tant qu'attribut

selector: '[any-directive]'

C'est le cas le plus courant ! En effet le plus souvent, les directives sont utilisées en tant qu'attribut.

Dans ce cas-ci, la directive doit être utilisée comme attribut de la balise :

1
<div any-directive></div>

Optionnellement, on peut aussi vouloir passer une valeur à cet attribut :

1
<div [any-directive]="valeur"></div>

Dans ce cas là on récupère la valeur passé grâce à un input (de même nom que le selector) dans la directive :

1
@Input() appAnyDirective= '';

Directive utilisée en tant que classe CSS

selector: '.any-directive'

Dans cet exemple on utilise la directive sous forme de classe CSS.

1
<div class="any-directive"></div>

Le paramètre inputs

Si je décide d'utiliser ma directive en tant que balise, elle pourra gérer des entrées/sorties.

Inputs permet donc d’utiliser des paramètres d’entrée dans la directive. Le nom du paramètre doit être sous forme de chaîne de caractères dans l'attribut inputs :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Directive, ElementRef, Renderer2, OnInit } from '@angular/core';

@Directive({
  selector: 'custom-directive',
  inputs: [ 'textToDisplay' ]
})

export class CustomDirective implements OnInit {

  textToDisplay: string;

  constructor(private elem: ElementRef, private renderer: Renderer2) {}

  ngOnInit(): void {
    let newText = this.renderer.createText(this.textToDisplay);
    this.renderer.appendChild(this.elem.nativeElement, newText);
  }
}

Ici, la propriété ElementRef.nativeElement nous permet d’accéder directement à l’objet du DOM.

Le paramètre de type Renderer2 est une classe permettant de manipuler les éléments du DOM.

Que faisons-nous concrètement ? On transmet la valeur à partir du template, pour qu'elle soit utilisée dans la directive :

1
<custom-directive textToDisplay="Valeur défini depuis le template"></custom-directive>

Le paramètre outputs

Output permet de déclarer des événements de la directive auxquels le composant peut s’abonner. Le nom de l'événement doit être sous forme de chaîne de caractères dans le paramètre outputs :

1
<custom-directive textToDisplay="Valeur défini depuis le template"></custom-directive>

Exemple

Prenons l'exemple d'une directive qui crée un bouton capable d'émettre un événement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Directive, ElementRef, Renderer2 } from '@angular/ core';

@Directive({
  selector: 'custom-directive',
  outputs: [ 'anyEvent' ]
})
export class CustomDirective {

  anyEvent: EventEmitter<string> = new EventEmitter<string>();
  constructor(private elem: ElementRef, private renderer: Renderer2) {
    let Button = this.renderer.createElement('button');
    let buttonText = this.renderer.createText('Cliquez');
    // On ajoute du texte au bouton
    this.renderer.appendChild(Button, buttonText);
    // On ajoute la callback correspondant au clique sur le bouton
    this.renderer.listen(Button, 'click', () => this.buttonClicked());
    // On ajoute le bouton à l'élément (le composant)
    this.renderer.appendChild(this.elem.nativeElement, Button);
  }

  private buttonClicked(): void {
    this.anyEvent.emit('message');
  }
}

Le paramètre providers

Enfin, nous avons le paramètre providers pour injecter des services dans la directive. Voir l’article sur l’injection de dépendance pour plus de détails.

Conclusion

Pour conclure, vous avez maintenant les connaissances qu’il faut pour savoir quand et pourquoi utiliser les directives, n'hésitez pas à poser des questions en commentaires !

Sources

Auteur

image auteur
Lucas BENTO-VERSACE Alternant chez Codewise / Étudiant en développement web lucas.bentoversace@ynov.com
"Alternant chez Codewise, passioné par l'informatique et les technologies depuis mon plus jeune âge. J'ai découvert le monde du web et depuis je ne m'en lasse pas ! Fan de jeux vidéos, de sports et de musiques. "