Angular - Les composants

Un composant Angular, à quoi ça sert ? Et comment s'en servir correctement ?

Un composant c'est quoi ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Component({
    selector: 'app-todo',
    templateUrl: './todo.component.html',  // or template: '<p>todo works!</p>',
    styleUrls: ['./todo.component.css'], // or styles: ['p { color: red }'],
    animations: [],
    encapsulation: ViewEncapsulation.Emulated,
    exportAs: 'todo'
})
export class TodoComponent  {
    @Input()
    options: any;
    @Output()
    notify: EventEmitter<string> = new EventEmitter<string>();
    todos: string[];
    constructor() { }
    addTodo(message: string) {
        this.todos.push(message);
    }
}
Exemple de composant Angular

En terme de concept

Un composant (component) est une unité de découpage visuel.

Les pages sont des composants, elles-mêmes découpées en plusieurs composants. L'idée est de séparer les pages web en composants pour ne pas se contenter de découper par langage (les fameux dossiers js, html, css ou autre).

On donne ainsi une structure logique au projet et à nos fichiers.

En terme de code

Un composant, c'est une classe Typescript avec des attributs, méthodes et constructeur et qui sert d'unité de découpage visuel dans un projet front. Il possède aussi des métadonnées qu'on lui injecte via la directive @Component.

On définit un composant dans Angular par un ensemble de ressources :

  • une portion de HTML
  • une ou des feuilles de styles associée(s) à ce HTML
  • une classe TypeScript ou JavaScript

Pour l'instancier, on utilise son sélecteur qui correspond à un tag HTML. Vous pouvez donc intégrer votre composant directement dans un bloc HTML, en utilisant son tag HTML associé.

Un composant est réutilisable autant de fois que nécessaire. Par conséquent, on ne code un composant qu'une fois pour plusieurs affichages.

On a également accès à son cycle de vie, via des hooks et il peut posséder n entrée/sortie pour communiquer avec d'autres composants.

Communication entre composants

Les méthodes dites view et content sont 2 approches différentes pour faire communiquer des composants.

La principale différence se trouve dans le partage du scope (la portée des variables) entre les composants parent et enfant.

La méthode view

  • Scope isolé entre les composants
  • Couplage composant parent/enfant lourd

Chaque composant gardant son scope isolé de l'autre, les directives @Inputs et @Outputs leur permettent de s’échanger des valeurs.

@Input

Input, permet de spécifier une valeur en entrée du composant. Pour l'utiliser, vous allez devoir modifier le code à 2 endroits :

  • Déclarez-le avec l'annotation @Input() dans le composant (fichier ts)
  • Liez-le en “one-way” binding avec [myInputVarNameHere] dans le template html du parent
1
2
3
4
5
6
7
8
@Component({
    selector: 'app-todo',
})
export class TodoComponent {
    @Input()
    todo: any;
    ...
}
todo.component.ts (enfant)
1
<app-todo [todo]="todo"></app-todo>
todo-list.component.html (parent)

@Output

@Output, permet de spécifier un événement en sortie du composant. Pour l'utiliser, vous devez modifier le code à 3 endroits :

  • Instanciez un attribut de type EventEmitter dans le composant (ts) qu'on décore avec la directive @Output
  • Liez-le en event-binding avec (myOutputVariableName) dans le template HTML du parent
  • Faîtes appel à la fonction emit() de l'EventEmitter au moment ou l'output doit se produire
1
2
3
4
5
6
7
8
@Component({
    selector: 'app-todo'
})
export class TodoComponent {
    @Output()
    notify: EventEmitter<any> = new EventEmitter();
    ...
}
todo.component.ts (enfant)
1
<app-todo (notify)="displayChecked($event)"></app-todo>
todo-list.component.html (parent)

La méthode content

  • pas d’input/output
  • scope partagé entre les composants
  • plus souple, plus facilement réutilisable

La balise ng-content

Pour mettre en place la projection de contenu de la méthode content, vous allez devoir modifier le code à 2 endroits :

  • Ajoutez du HTML à l'intérieur des balises de l'enfant, dans le fichier HTML du parent
  • Récupérez-le dans le composant enfant, en projetant son contenu dans une balise <ng-content>
1
2
3
4
5
6
7
8
9
@Component({
    selector: 'fa-input',
    template: `
            <i class="fa" [ngClass]="classes"></i>
            <ng-content></ng-content>
`
})
export class FaInputComponent {
}
fa-input.component.ts (composant enfant)
1
2
3
4
<h1>FA Input</h1>
<fa-input>
    <input type=”email” placeholder=”Email”/>
</fa-input>
app.component.html (parent)

Vous remarquez que, dans le composant parent, on a placé du contenu html à l'intérieur de notre balise de composant <fa-input>

Ce contenu est rendu disponible dans le composant fils via la balise <ng-content>. On dit que le contenu de la balise <fa-input> (parent) est projeté dans la balise <ng-content>

Vous connaissez maintenant les principes de la méthode view et de la projection de contenu !

Dans quel cas l'utiliser ?

La plupart du temps, on utilise la méthode view avec des Input/Output pour structurer la circulation des données au sein d'une page. Cela s'explique car le couplage entre les composants est fort, il s'agit d'un découpage spécifique à la page. De plus on souhaite isoler les scopes au possible.

Mais dans certains cas, il n'est vraiment pas pratique de garder des scopes isolés, et donc d'utiliser la méthode view.

Par exemple, si vous comptez réutiliser un composant souvent et que vous devez accéder à 5 de ses attributs, vous allez devoir gérer beaucoup d'Input/Output. C'est souvent le cas quand vous voulez séparer des formulaires sur plusieurs composants, ou rendre un formulaire générique.

Pour ces cas-là, vous pouvez choisir la méthode content.

Vous aurez donc un couplage plus faible mais une séparation néante des scopes entre les composants (le scope de l'enfant n'existe pas, celui du parent contient tout).

Si vous souhaitez bien comprendre les différents cas d'utilisation, je vous recommande ce tutoriel d'Angular University : Tutoriel pour maitriser la différence view/content

Passons maintenant au cycle de vie des composants !

Cycle de vie d'un composant

Cycle de vie d'un composant Angular

Angular vous permet d'exécuter du Javascript lors de certains évènements liés au cycle de vie du component.

Cela peut s'avérer utile, par exemple pour lancer un appel API au chargement d'une page ou bien pour supprimer une ressource lors de la destruction de l'instance du composant.

ngOnChange

  • ne s’exécute pas si pas d’@Input
  • 1er hook appelé après l’appel au constructeur
  • déclenché à chaque changement de donnée en entrée @Input()

ngOnInit

  • intervient après l’appel de ngOnChange() 
  • déclenché après l’initialisation du composant
  • appelé uniquement une fois

ngDoCheck

  • intervient après l’appel de ngOnInit() 
  • ne s’exécute pas si ngOnChange est implémentée

ngAfterContentInit

  • s'exécute lorsqu’un content (ng-content) est initialisé
  • @ContentChildren et @ContentChild sont valorisées

ngAfterContentChecked

  • exécuté à chaque détection de changement
  • @ContentChildren et @ContentChild sont valorisées

ngAfterViewInit

  • exécuté une fois les vues enfants initialisé

ngAfterViewChecked

  • exécuté à chaque détection de changement
  • @ViewChildren et @ViewChild sont valorisées

ngOnDestroy

  • exécuté avant la suppression du composant
La prochaine étape, quand vous êtes à l'aise avec le concept de composants : Les services Angular.

Sources