Cet article fait partie de la série d’articles Angular from Scratch.
Pour récupérer l’instance d’un objet se trouvant sur la vue dans la classe d’un composant, il est possible d’effectuer des requêtes auprès de cette vue et renseigner un membre ou une propriété de la classe avec l’instance de cet objet. L’objet requêté peut être un composant enfant, une directive ou un objet du DOM.
Pour effectuer un requêtage sur la vue, on peut s’aider de plusieurs décorateurs: @ViewChild()
, @ViewChildren()
, @ContentChild()
ou @ContentChildren()
.
Ces décorateurs se placent devant la propriété ou le membre de la classe du composant dans lesquels l’instance doit être renseignée.
En Javascript, l’équivalent de ces décorateurs pourrait être:
document.getElementById('id-element');
Le choix du décorateur à utiliser dépend du type d’objet à requêter:
@ViewChild()
et@ViewChildren()
permettent de requêter un objet de la vue. Cet objet peut être un objet Angular ou un objet du DOM.@ViewChild()
retourne le 1er objet correspondant aux identifiants indiqués en argument;@ViewChildren()
renvoie une liste d’objets correspondants.@ContentChild()
et@ContentChildren()
retournent un ou plusieurs objets se trouvant dans le composant par projection de contenu. A la différence de@ViewChild()
, le contenu par projection n’est pas initialisé au même moment que les autres éléments de la vue d’un composant.@ContentChild()
retourne le 1er objet correspondant aux identifiants;@ContentChildren()
renvoie une liste d’objets correspondants.
Requêter un élément d’une vue
@ViewChild()
Requêter un composant enfant
Requêter une directive
Requêter un objet du DOM
@ViewChildren()
Requêter les objets suivant leur type
Requêter les objets suivant leur nom
Requêter un contenu projeté
@ContentChild()
Requêter les objets suivant leur type
@ContentChildren()
Requêter les objets suivant leur type
Requêter les objets suivant leur nom
Paramètre descendants dans @ContentChildren()
Requêter un élément d’une vue
Pour requêter un élément de la vue d’un composant, il faut utiliser @ViewChild()
ou @ViewChildren()
.
@ViewChild()
Le décorateur @ViewChild()
permet d’accéder à un élément implémenté dans le template d’un composant:
- Si l’élément est un composant enfant alors
@ViewChild()
permettra d’accéder à l’instance de ce composant. - Si l’élément est un objet du DOM alors
@ViewChild()
permettra d’accéder à cet objet par l’intermédiaire d’un objet de typeElementRef
.
Pour comprendre l’intérêt de @ViewChild()
, dans un premier temps imaginons que l’on souhaite imbriquer un composant dans un autre. Plusieurs syntaxes sont possibles:
- en utilisant le paramètre
selector
du composant à imbriquer ou - par projection de contenu.
Ces 2 méthodes permettent d’effectuer l’imbrication directement à partir des fichiers templates sans effectuer d’implémentation particulière du coté des classes des composants.
Par exemple, si on considère 2 composants ChildComponent
et ParentComponent
utilisés respectivement pour être le composant enfant et le composant parent. Pour imbriquer le composant ChildComponent
dans le composant ParentComponent
en utilisant le paramètre selector
, l’implémentation pourrait être:
- Pour le composant enfant:
Template <p>Child component</p>
Classe du composant import { Component } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component' }) export class ChildComponent() {}
- Pour le composant parent:
Template <p>Parent component</p> <app-child></app-child>
Classe du composant import { Component } from '@angular/core'; @Component({ templateUrl: './parent.component' }) export class ParentComponent() {}
Avec cette implémentation, on peut faire référence au composant enfant à partir du template du composant parent en utilisant une variable référence. Si on souhaite accéder à une propriété du composant enfant pour l’afficher, on peut utiliser l’implémentation suivante:
- Pour le composant enfant:
Template <p>Child component</p>
Classe du composant import { Component } from '@angular/core'; @Component({ selector: 'app-child', templateUrl: './child.component' }) export class ChildComponent() { internalValue = 'Value to display'; }
- Pour le composant parent:
Template <p>Parent component</p> <app-child #child></app-child> <p>Child internal value: {{child.internalValue}}</p>
Classe du composant import { Component } from '@angular/core'; @Component({ templateUrl: '/parent.component' }) export class ParentComponent() {}
Si on souhaite accéder au membre internalValue
du composant enfant à partir de la classe du composant parent, il n’y a pas de méthode directe.
Le but du décorateur @ViewChild()
est de donner une méthode pour accéder à un composant utilisé dans le template.
Plus généralement @ViewChild()
permet d’accéder à un composant, une directive ou un objet du DOM implémenté dans le template à partir de la classe du composant. Ainsi en préfixant une propriété avec le décorateur, la propriété sera automatiquement bindée avec l’objet se trouvant dans le template.
Requêter un composant enfant
En reprenant l’exemple précédent, on ajoute dans la classe du composant parent le membre childReference
avec le décorateur @ViewChild()
:
Template |
|
Classe du composant |
|
Ainsi la propriété childReference
est automatiquement bindée avec l’instance du composant enfant seulement quand l’évènement AfterViewInit
ou OnInit
est déclenché suivant la valeur de static
.
ngAfterViewInit()
ou ngOnInit()
Quand on utilise le décorateur @ViewChild()
, le binding de l’élément n’est pas effectué dès la construction de la classe mais après le déclenchement des callbacks ngAfterViewInit()
ou ngOnInit()
suivant la valeur du paramètre static
(pour plus de détails voir Paramètre static):
@ViewChild(<type de l'objet>, { static: false })
le binding sera effectué quand la callbackngAfterViewInit()
est déclenchée. La classe du composant doit dériver deAfterViewInit
:export class ParentComponent implements AfterViewInit { ngAfterViewInit(): void {} }
@ViewChild(<type de l'objet>, { static: true })
le binding sera effectué quand la callbackngOnInit()
est déclenchée. La classe du composant doit dériver deOnInit
:export class ParentComponent implements OnInit { ngOnInit(): void {} }
Requêter une directive
La syntaxe est indentique à celle utilisée avec les composants. Par exemple si on considère la directive suivante:
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[contentFiller]'
})
export class SimpleDirective {
constructor(private elem: ElementRef, private renderer: Renderer2) {
let newText = renderer.createText('Content from directive');
renderer.appendChild(elem.nativeElement, newText);
}
}
Il s’agit d’une directive attribut (i.e. attribute directive) rajoutant le texte 'Create from directive'
dans son élément hôte.
Pour appeler la directive à partir du composant hôte, le template du composant est:
<p>Parent component</p>
<p contentFiller #directiveHost></p>
En utilisant @ViewChild()
avec la variable référence #directiveHost
dans la classe du composant, on obtient l’implémentation suivante:
import { Component, AfterViewInit, ViewChild } from '@angular/core';
import { SimpleDirective } from '../simple.directive';
@Component({
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements AfterViewInit {
@ViewChild('directiveHost', { read: SimpleDirective }) innerDirective: SimpleDirective;
constructor() { }
ngAfterViewInit(): void {
console.log(this.innerDirective);
}
}
On remarque quand dans cet exemple, on utilise l’option { read: SimpleDirective }
dans la directive @ViewChild()
de façon à effectuer la résolution avec le directive. Si on ne précise pas cette option, la résolution se fera sur l’élément hôte de la directive.
Dans cet exemple, l’élément hôte de la directive est une élément HTML <p></p>
.
Requêter un objet du DOM
@ViewChild()
permet de binder un objet du DOM avec un membre du composant.
Par exemple, si on considère le code suivant pour la classe parente:
Template |
|
Classe du composant |
|
Ainsi, avec une variable référence pour désigner l’objet span
, on peut utiliser le décorateur @ViewChild()
pour binder l’objet du DOM avec le membre spanReference
. Le binding sera effectué quand la callback ngAfterViewInit()
ou ngOnInit()
est déclenchée suivant la valeur de static
.
L’objet du DOM est wrappé dans un objet de type ElementRef
. Cet objet permet de récupérer un élément du DOM en utilisant la propriété nativeElement
.
@ViewChildren()
Le décorateur @ViewChildren()
permet de requêter la vue pour retourner les instances d’objets s’y trouvant. La différence avec ViewChild()
est que @ViewChildren()
renvoie tous les objets satisfaisants les conditions de la requête (@ViewChild()
ne retourne que le 1er objet).
@ViewChildren()
peut être utilisé pour retourner un composant ou une directive en précisant le type de l’objet dans le paramètre selector
. Il est possible de requêter plusieurs objets en précisant plusieurs noms.
Requêter les objets suivant leur type
Si le paramètre selector
est un type alors tous les objets correspondant à ce type seront retournés.
Par exemple, si on considère l’exemple suivant:
- Le composant
ChildComponent
:
Template <p>Child Component</p>
Classe du composant @Component({ selector: 'child', templateUrl: './child.component' }) export class ChildComponent {}
- Le composant
ParentComponent
:
Template <p>Parent Component</p> <child></child> <child></child> <child></child>
Classe du composant import { Component, AfterViewInit, ViewChildren, QueryList } from '@angular/core'; import { ChildComponent } from '../child/child.component'; @Component({ templateUrl: './parent.component.html' }) export class ParentComponent implements AfterViewInit { @ViewChildren(ChildComponent) childReferences: QueryList<ChildComponent>; ngAfterViewInit(): void { console.log(this.childReferences); } }
Dans ce cas, childReferences
contient les 3 références de composant enfant ChildComponent
.
Le résultat est:
Parent Component
Child Component
Child Component
Child Component
Requêter les objets suivant leur nom
Il est possible de requêter les objets en précisant les noms de ces objets en utilisant la syntaxe:
@ViewChildren('<nom variable ref 1>, <nom variable ref 2>, ..., <nom variable ref N>') references: QueryList<T>;
Par exemple:
Template |
|
Classe du composant |
|
Le résultat est le même que précédemment.
Requêter un contenu projeté
Pour requêter un contenu projeté dans la vue d’un composant, il faut utiliser @ContentChild()
ou @ContentChildren()
(voir Les composants enfant pour plus de détails sur la projection de contenu).
@ContentChild()
Le décorateur @ContentChild()
permet de requêter la vue d’un composant dans le cadre d’une projection de contenu. Comme le contenu projeté provient de l’extérieur du composant, utiliser @ViewChild()
ne permettra pas de requêter le contenu projeté car @ViewChild()
effectue la recherche parmi les éléments du composant définis directement dans sa vue.
Si on prend l’exemple suivant:
- Le composant
ParentComponent
projette un contenu dans le composantChildComponent
. - Le composant
ChildComponent
affiche le contenu projeté en utilisant<ng-content>
.
- Le code de
ChildComponent
est:
Template <p>Child Component</p> <ng-content></ng-content>
Classe du composant @Component({ selector: 'child', templateUrl: './child.component.html' }) export class ChildComponent {}
- Le code de
ParentComponent
est:
Template <p>Parent Component</p> <child> <p #projectedContent>Projected content</p> </child>
Classe du composant @Component({ templateUrl: './parent.component.html' }) export class ParentComponent {}
L’affichage de cet exemple est:
Parent Component
Child Component
Projected Content
Dans ChildComponent
, on pourra requêter le contenu projeté grâce à <ng-content></ng-content>
avec le décorateur @ContentChild()
:
Template |
|
Classe du composant |
|
Le résultat sera affiché dans la console du browser.
ngAfterContentInit()
Quand on utilise @ContentChild()
, le membre ou la propriété sera affecté juste avant le déclenchement de la callback ngAfterContentInit()
.
Requêter les objets suivant leur type
L’exemple précédent permettait d’effectuer une requête en utilisant le nom d’une variable référence toutefois, comme pour @ViewChild()
, il est possible d’utiliser un type dans le paramètre selector
de @ContentChild()
.
Par exemple, si on ajoute le composant OtherComponent
:
Template |
|
Classe du composant |
|
On peut effectuer une requête pour récupérer l’instance du composant OtherComponent
se trouvant dans le contenu projeté:
Template |
|
Classe du composant |
|
Le code de ParentComponent
est:
Template |
|
Classe du composant |
|
Comme pour @ViewChild()
, le requêtage en utilisant un type s’applique plus généralement aux directives et pas seulement sur les composants. Dans l’exemple précédent, on aurait pû utiliser une directive au lieu d’utiliser le composant OtherComponent
.
@ContentChildren()
Le décorateur @ContentChildren()
permet de requêter le contenu projeté d’un composant pour retourner toutes les instances des objets satisfaisants aux conditions de la requête. La différence avec @ContentChild()
est que @ContentChildren()
renvoie toutes les instances satisfaisants aux conditions et pas seulement le 1er objet trouvé comme pour @ContentChild()
.
@ContentChildren()
peut être utilisé pour retourner un composant ou une directive en précisant le type de l’objet dans le paramètre selector
. Il est possible de requêter plusieurs objets en précisant plusieurs noms.
Requêter les objets suivant leur type
L’exemple suivant permet de montrer comment requêter des objets suivant leur type. Dans cet exemple, plusieurs instances de OtherComponent
sont projetés dans le composant ChildComponent
. En utilisant le décorateur @ContentChildren()
, on peut retourner une liste de toutes les instances de OtherComponent
qui ont été projetées:
Template |
|
Classe du composant |
|
On peut effectuer une requête pour récupérer l’instance du composant OtherComponent
se trouvant dans le contenu projeté:
- Le code du composant
ChildComponent
:
Template <p>Child Component</p> <ng-content></ng-content>
Classe du composant import { Component, AfterContentInit, ContentChildren, ElementRef, QueryList } from '@angular/core'; import { OtherComponent } from '../other/other.component'; @Component({ selector: 'child', templateUrl: './child.component.html' }) export class ChildComponent implements AfterContentInit { @ContentChildren(OtherComponent) projectedContent: QueryList<OtherComponent>; ngAfterContentInit(): void { console.log(this.projectedContent); } }
- Le code de
ParentComponent
est:
Template <p>Parent Component</p> <child> <!-- Instance 1 --> <other></other> <!-- Instance 2 --> <other></other> <!-- Instance 3 --> <other></other> </child>
Classe du composant @Component({ templateUrl: './parent.component.html' }) export class ParentComponent {}
Le résultat est:
Parent Component
Child Component
Other Component
Other Component
Other Component
Requêter les objets suivant leur nom
Il est possible de requêter les objets en précisant les noms de ces objets en utilisant la syntaxe:
@ContentChildren('<nom variable ref 1>, <nom variable ref 2>, .., <nom variable ref N>') references: QueryList<T>;
Par exemple:
Template |
|
Classe du composant |
|
On peut effectuer une requête pour récupérer l’instance du composant OtherComponent
se trouvant dans le contenu projeté:
- Le code de
ChildComponent
:
Template <p>Child Component</p> <ng-content></ng-content>
Classe du composant import { Component, AfterContentInit, ContentChildren, ElementRef, QueryList } from '@angular/core'; import { OtherComponent } from '../other/other.component'; @Component({ selector: 'child', templateUrl: './child.component.html' }) export class ChildComponent implements AfterContentInit { @ContentChildren('instance1, instance2, instance3') projectedContent: QueryList<OtherComponent>; ngAfterContentInit(): void { console.log(this.projectedContent); } }
- Le code de
ParentComponent
est:
Template <p>Parent Component</p> <child> <other #instance1></other> <other #instance2></other> <other #instance3></other> </child>
Classe du composant @Component({ templateUrl: './parent.component.html' }) export class ParentComponent {}
Le résultat est le même que précédemment.
@ContentChild()
et @ContentChildren()
effectuent une recherche dans le DOMLes décorateurs @ContentChild()
et @ContentChildren()
permettent d’effectuer une requête dans le contenu projeté dans le DOM. Cela ne veut pas dire que le contenu doit être affiché dans la vue correspondante ou que <ng-content></ng-content>
doit être présent.
Par exemple, si on écrit le code suivant:
- Pour un composant enfant:
Template <p>Child component</p>
Classe du composant import { Component, ElementRef, ContentChild, AfterContentInit } from '@angular/core'; @Component({ selector: 'child', templateUrl: './child.component.html' }) export class ChildComponent implements AfterContentInit { @ContentChild('content') content: ElementRef; ngAfterContentInit(): void { console.log(this.content); } }
- Pour le composant parent:
Template <p>Parent component</p> <child> <p #content>Content</p> </child>
Classe du composant import { Component } from '@angular/core'; @Component({ selector: 'parent', templateUrl: './parent.component.html' }) export class ParentComponent {}
On peut voir qu’il n’y a pas <ng-content></ng-content>
et que le contenu projeté n’est pas visible:
Parent Component
Child Component
Pourtant le contenu projeté se trouve dans le DOM et @ContentChild()
permet de récupérer l’objet correspondant se trouvant dans le DOM.
L’objet du DOM se trouve dans la propriété this.content.nativeElement
. On peut voir que la valeur de la propriété this.content.nativeElement.isConnected
est false
et que this.content.nativeElement.parentNode
est undefined
expliquant pourquoi cet objet n’est pas visible.
Paramètre descendants dans @ContentChildren()
Le paramètre descendants
permet d’indiquer si la requête porte sur les éléments se trouvant directement dans le contenu projeté ou s’il faut descendre parmi les descendants dans la hiérarchie HTML des éléments.
Par exemple si on considère la directive suivante:
import { Directive, ElementRef, ContentChildren, AfterContentInit, QueryList } from '@angular/core';
@Directive({
selector: 'custom-directive',
templateUrl: './custom.component.html'
})
export class CustomDirective implements AfterContentInit {
@ContentChildren('content') content: QueryList<ElementRef>
ngAfterContentInit(): void {
console.log(this.content);
}
}
Et le composant suivant:
Template |
|
Classe du composant |
|
Dans ce cas, @ContentChildren()
ne permettra pas de récupérer l’élément HTML p
qui est projeté car il se trouve dans un élément HTML div
:
<div>
<p #content>Content</p>
</div>
Par défaut, @ContentChildren()
ne descend pas dans l’arbre hiérarchique des éléments HTML et la valeur du paramètre descendants
est false
: { descendants: false }
. Pour que @ContentChildren()
puisse effectuer la requête en considérant des éléments HTML plus bas dans la hiérarchie, il faut rajouter l’option { descendants: true }
dans @ContentChildren()
:
@Directive({
...
})
export class CustomDirective implements AfterContentInit {
@ContentChildren('content', { descendants: true }) content: QueryList<ElementRef>
ngAfterContentInit(): void {
console.log(this.content);
}
}
Avec l’option { descendants: true }
, l’élément p
sera récupéré par @ContentChildren()
.
Paramètre read
Ce paramètre s’utilise avec les décorateurs @ViewChild()
, @ViewChildren()
, @ContentChild()
ou @ContentChildren()
. Il permet d’ajouter un critère à la requête faite pour retourner l’objet de la vue. Le paramètre read
permet d’indiquer un type de l’objet qui sera retourné. Ainsi si plusieurs objets existent avec le même nom de variable référence, l’objet retourné correspondra au type précisé par read
.
Pour chaque élément se trouvant dans la vue, il existe un objet Angular correspondant de type ElementRef<any>
ou ViewContainerRef
. Avec le paramètre read
, on peut alors préciser le type ElementRef<any>
ou ViewContainerRef
et obtenir l’instance de l’objet correspondant.
Si cet élément est un composant, en précisant le type du composant avec read
, il est possible de retourner directement l’instance du composant.
L’exemple suivant permet de montrer que des éléments avec les mêmes identifiants dans la vue peuvent être utilisés de façon différente suivant la valeur du paramètre read
.
On considère le composant suivant qui servira de composant enfant:
Template |
|
Classe du composant |
|
On implémente un 2e composant qui servira de parent:
- Le fichier template est:
<p>Read example component</p> <p #content>Element content</p> <child-component #child></child-component>
- La classe du composant est:
import { Component, ViewChild, ElementRef, ViewContainerRef, AfterViewInit, ViewContainerRef } from '@angular/core'; import { ChildComponent } from '../child/child.component'; @Component({ templateUrl: './readexample.component.html' }) export class ReadExampleComponent implements AfterViewInit { @ViewChild('content', { read: ElementRef }) contentElementRef: ElementRef; @ViewChild('content', { read: ViewContainerRef }) contentViewContainerRef: ViewContainerRef; @ViewChild('child', { read: ChildComponent }) child: ChildComponent; @ViewChild('child', { read: ElementRef }) childElementRef: ElementRef; @ViewChild('child', { read: ViewContainerRef }) childViewContainerRef: ViewContainerRef; ngAfterViewInit(): void { console.log(this.contentElementRef); console.log(this.contentViewContainerRef); console.log(this.child); console.log(this.childElementRef); console.log(this.childViewContainerRef); } }
A l’affichage du composant ReadExampleComponent
, on peut voir dans la console du browser les différentes formes des objets affichés:
- Dans le cas de l’élément
p
avec la variable référencecontent
:- La propriété
contentElementRef
de typeElementRef
qui est un objet Angular wrappant l’objet du DOM correspondant. - La propriété
contentViewContainerRef
de typeViewContainerRef
qui est un objet Angular wrappant la vue correspondant à l’élémentp
.
- La propriété
- Dans le cas du composant enfant
ChildComponent
:- Les objets équivalents de type
ElementRef
etViewContainerRef
. child
contenant l’instance du composantChildComponent
. La ligne@ViewChild()
permettant d’effectuer le binding avecchild
peut être simplifié en:@ViewChild(ChildComponent) child: ChildComponent;
- Les objets équivalents de type
Paramètre static
Le paramètre static
peut être utilisé avec les décorateurs @ViewChild()
, @ViewChildren()
, @ContentChild()
ou @ContentChildren()
. Il permet d’indiquer quand doit être effectué la requête sur la vue. Pour comprendre pleinement le sens de ce paramètre, il faut avoir en tête le cycle de vie d’un composant et le fonctionnement de la détection de changements d’Angular. Le choix de la valeur de ce paramètre n’est pas anodin, un mauvais choix de sa valeur peut entraîner un échec de la requête sur la vue.
Pour résumer, Angular construit la vue d’un composant suivant 2 étapes:
- Création du contenu statique de la vue: cette étape n’est effectuée qu’à l’initialisation d’un composant. Elle permet de créer les éléments statiques de la vue c’est-à-dire les éléments qui ne seront pas modifiés par la détection de changements. Cette étape n’est exécutée qu’une seule fois de façon à optimiser le traitement, étant donné que les éléments sont statiques, il n’est pas nécessaire de les mettre à jour.
- Mise à jour du contenu dynamique: cette étape est répétée à chaque exécution de la détection de changements dans le cas où un changement a été détecté. Elle permet de mettre à jour les éléments d’une vue pouvant être affectée après la mise à jour d’une propriété du composant.
Le cycle de vie d’un composant découle directement du mécanisme de détection de changements. L’algorithme de détection de changements effectue les mises à jour des éléments graphiques suivant un ordre précis. Tout au long de ces mises à jour et suivant les éléments qui sont mis à jour, il va aussi exécuter les callbacks du cycle de vie des composants (i.e. Lifecycle hooks). L’ordre de déclenchement de ces callbacks est le suivant:
- A l’initialisation du composant:
ngOnChanges()
: cette callback est exécutée si le composant contient des propriétés en entrée (notamment avec le décorateur@Input()
).ngOnInit()
: déclenchée après l’exécution du constructeur. Il permet d’initialiser le composant avec le 1er affichage des données de la vue ayant un binding avec des propriétés de la classe du composant. Le cas échéant, il permet d’affecter les paramètres en entrée du composant. Cette callback est déclenchée une seule fois à l’initialisation du composant même singOnChanges()
n’est pas déclenchée.ngDoCheck()
permet d’indiquer des changements si Angular ne les a pas détecté.ngAfterContentInit()
est déclenchée à l’initialisation après la projection de contenu. Elle est déclenchée même s’il n’y a pas de contenu à projeter.ngAfterContentChecked()
: déclenchée après la détection de changement dans le contenu projeté. Cette callback est déclenchée même s’il n’y a pas de projection de contenu.ngAfterViewInit()
: déclenchée après l’initialisation de la vue du composant et après l’initialisation de la vue des composants enfant.ngAfterViewChecked()
est déclenchée après détection d’un changement dans la vue du composant et dans la vue des composants enfant.ngOnDestroy()
est déclenchée avant la destruction du composant.
- A chaque détection de changements, les callbacks déclanchées sont, dans l’ordre:
ngOnChanges()
si les paramètres en entrée du composant sont modifiés.ngDoCheck()
ngAfterContentChecked()
est déclenchée même s’il n’y a pas de contenu projeté.ngAfterViewChecked()
A l’initialisation d’un composant, le DOM est mis à jour à 2 reprises: lors de la création du contenu statique de la vue et lors de la mise à jour du contenu dynamique. On peut résumer cette mise à jour, l’exécution des callbacks du cycle de vie et l’exécution des requêtes sur les vues dans le schéma suivant:
|
Ce schéma permet de se rendre compte à quelle stade les requêtes sont exécutées suivant la valeur du paramètre static
.
Par exemple, si on considère le composant suivant:
Template |
|
Classe du composant |
|
Dans le template, on affiche la propriété miscNumber
du composant. Dans le classe du composant, on effectue une requête sur la vue en utilisant la variable référence #numberElement
. On implémente les callbacks ngOnInit()
, ngAfterContentInit()
et ngAfterViewInit()
pour afficher le contenu de l’élément p
identifié par la variable #numberElement
.
Après exécution, seule la ligne suivante apparaît dans la console du browser:
From ngAfterViewInit(): 5
Par défaut, si le paramètre static
n’est pas renseigné dans la requête @ViewChild()
, sa valeur est false
. Cela signifie que la requête sera exécutée peu avant le déclenchement de la callback ngAgterViewInit()
. Ainsi, le résutat de l’exécution s’explique par le fait que pour les autres callbacks, la requêtes @ViewChild()
n’a pas été exécutée et la propriété numberElementRef
est encore indéfinie. Le contenu de l’élément #numberElement
ne peut donc pas être affiché.
Si on modifie la valeur du paramètre static
telle que:
@ViewChild('numberElement', { static: true }) numberElementRef:
ElementRef<HTMLParagraphElement>;
Le résultat de l’exécution devient:
From ngOnInit():
From ngAfterContentInit():
From ngAfterViewInit(): 5
Ainsi, avec { static: true }
, la requête @ViewChild()
est exécutée juste avant le déclenchement de la callback ngOnInit()
sur le contenu statique de la vue. Ce contenu contient l’élément p
toutefois le résultat de l’interpolation {{miscNumber}}
ne fait pas partie du contenu statique de la vue. La priopriété this.numberElementRef
est définie mais this.numberElementRef.nativeElement.textContent
est vide car le DOM n’a pas été mis à jour avec le contenu dynamique de la vue.
Une fois que le DOM a été mis à jour avec le contenu dynamique de la vue juste avant le déclenchement de la callback ngAfterViewInit()
, this.numberElementRef.nativeElement.textContent
contient le résultat de l’interpolation. Ainsi ngAfterViewInit()
permet d’afficher le résultat de l’interpolation.
Cet exemple permet de montrer que suivant la valeur du paramètre static
, l’exécution des requêtes sur les vues n’est pas effectuée au même stade:
- Si
static
esttrue
alors la requête est effectuée sur le contenu statique. Le résultat de la requête est disponible en amont du cycle de vie du composant. - Si
static
estfalse
alors la requête est effectuée sur le contenu dynamique (elle peut aussi être exécutée sur le contenu statique). Le résultat de la requête est disponible plus tard dans le cycle de vue du composant.
Paramètre queries de @Directive()
Le paramètre queries
est utilisable dans les décorateurs @Directive()
et @Component()
(puisque @Component()
hérite de @Directive()
). Ce paramètre permet d’effectuer les mêmes traitements qu’avec les décorateurs @ViewChild()
, @ViewChildren()
, @ContentChild()
et @ContentChildren()
, toutefois la syntaxe rend la lecture moins facile.
Au lieu d’utiliser les décorateurs devant les membres ou propriétés, on instancie les objets qui vont renseigner les valeurs des membres ou des propriétés. Les objets instanciés sont équivalents aux décorateurs: @ViewChild()
, @ViewChildren()
, @ContentChild()
ou @ContentChildren()
.
Par exemple, si considère 2 composants ChildComponent
et ParentComponent
tels que:
ChildComponent
:
Template <p>Child component</p>
Classe du composant import { Component, ElementRef, ContentChild, AfterContentInit } from '@angular/core'; @Component({ selector: 'child', templateUrl: './child.component.html', queries: { content: new ContentChild('content') } }) export class ChildComponent implements AfterContentInit { content: ElementRef; ngAfterContentInit(): void { console.log(this.content); } }
- Pour le code du composant parent:
Template <p>Parent component</p> <child #child> <p #content>Content</p> </child>
Classe du composant import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from '../child/child.component'; @Component({ selector: 'parent', templateUrl: './parent.component.html' queries: { child: new ViewChild(ChildComponent) } }) export class ParentComponent implements AfterViewInit { child: ChildComponent; ngAfterViewInit(): void { console.log(this.child); } }
Dans cet exemple, on utlise le paramètre queries
dans des décorateurs @Component()
pour instancier:
ContentChild()
dansChildComponent
pour renseigner le membrecontent
de typeElementRef
contenant un objet Angular correspondant à l’élément HTML<p #content></p>
projeté à partir deParentComponent
.@ViewChild()
dansParentComponent
pour renseigner le membrechild
de typeChildComponent
contenant l’instance du composant enfant se trouvant dans le template deParentComponent
.
Pour résumer…
Requêter la vue d’un composant permet de récupérer l’instance d’un objet de façon à l’exploiter dans la classe du composant. Les objets requêtés peuvent être un composant enfant, une directive ou objet quelconque du DOM.
Le résultat des requêtes permet d’affecter des propriétés dans la classe du composant. Pour indiquer qu’une propriété doit être initialisée avec le résultat d’une requête sur la vue, il faut utiliser des décorateurs particuliers:
@ViewChild()
pour effectuer une requête sur un seul objet directement dans la vue du composant. Seul le 1er objet de la vue satisfaisant la requête est renvoyé.@ViewChildren()
pour effectuer une requête sur une liste d’objets directement dans la vue du composant. Tous les objets satisfaisant la requête sont renvoyés.@ContentChild()
pour effectuer une requête sur un seul objet se trouvant dans du contenu projeté (i.e. content projection) sur la vue du composant. Seul le 1er objet de la vue satisfaisant la requête est renvoyé.@ContentChildren()
pour effectuer une requête sur une liste d’objets se trouvant dans du contenu projeté (i.e. content projection) sur la vue du composant. Tous les objets satisfaisant la requête sont renvoyés.
Les critères de la requête peuvent être de nature différente.
Requêter suivant un type
2 méthodes sont possibles pour spécifier le type de l’objet à requêter:
- Spécifier directement le type en tant qu’argument du décorateur utilisé, par exemple pour requêter une directive dont le type est
TypedDirective
:@ViewChild(TypedDirective) requestedDirective: TypedDirective;
Dans cet exemple, on a utilisé
@ViewChild()
toutefois les autres décorateurs utilisent la même syntaxe. - Dans le cas où on effectue une requête avec le nom d’une variable référence, on peut préciser le type attendu de l’objet en l’indiquant en utilisant l’option
read
.Par exemple, pour requêter une directive avec une variable référence
'directiveRef'
et dont le type estTypedDirective
:@ViewChild('directiveRef', { read: TypedDirective }) requestedDirective: TypedDirective;
Requêter suivant une variable référence
Si l’objet est identifié dans la vue en utilisant une variable référence, on peut requêter en utilisant le nom de cette variable.
Par exemple, pour requêter une directive avec une variable référence 'directiveRef'
:
@ViewChild('directiveRef') requestedDirective: TypedDirective;
Indiquer si l’objet fait partie du contenu statique de la vue
Le contenu de la vue est séparé en 2 parties:
- Un contenu statique: ce contenu permet de mettre à jour le DOM seulement à l’initialisation de la vue. Ce contenu est initialisé juste avant le déclenchement des callbacks
ngOnInit()
et/oungDoCheck()
du cycle de vie du composant. - Un contenu dynamique: cette partie de la vue est mise à jour à chaque détection de changement. Ce contenu est initialisé et mis à jour à des périodes différentes suivant l’objet requêté:
- Si l’objet se trouve directement dans la vue du composant: il est requêté avec
@ViewChild()
ou@ViewChildren()
. Le contenu dynamique de cet objet est initialisé et mis à jour juste avant le déclenchement des callbacksngAfterViewInit()
et/oungAfterViewChecked()
. - Si l’objet se trouve dans du contenu projeté: il est requêté avec
@ContentChild()
ou@ContentChildren()
. Le contenu dynamique de cet objet est initialisé et mis à jour avant le déclenchement des callbacksngAfterContentInit()
et/oungAfterContentChecked()
.
- Si l’objet se trouve directement dans la vue du composant: il est requêté avec
Un objet peut être requêté dans le contenu statique ou dynamique de la vue suivant la valeur de l’option static
:
{ static: false }
: valeur par défaut, elle permet de requêter le contenu statique et dynamique d’une vue. Le résultat de cette requête est disponible dans la propriété au déclenchement des callbacks:ngAfterContentInit()
et/oungAfterContentChecked()
pour du contenu projeté.ngAfterViewInit()
et/oungAfterViewChecked()
pour une requête directement sur la vue.
{ static: true }
: permet de requêter seulement le contenu statique d’une vue. Le résultat de cette requête est disponible dans la propriété au déclenchement des callbacksngOnInit()
et/oungDoCheck()
.Par exemple, pour requêter un élément HTML
p
nommé'content'
dans le contenu statique de la vue:@ViewChild('content', { static: true }) contentRef: ElementRef<HTMLParagraphElement>;
QueryList
Si on utilise @ViewChildren()
ou @ContentChildren()
pour requêter une liste d’objets, on peut utiliser la liste QueryList
pour stocker la liste des objets.
Par exemple, pour requêter des directives de type TypedDirective
directement sur la vue d’un composant:
@ViewChildren(TypedDirective) requestedDirectives: QueryList<TypedDirective>;
Option descendants
L’option { descendants: true }
utilisable avec @ContentChildren()
permet d’indiquer si la requête doit porter sur tous les descendants d’un élément dans la hiérarchie HTML. Par défaut, la requête est effectuée seulement sur les enfants directs.
- Angular @ViewChild: In-Depth Explanation (All Features Covered): https://blog.angular-university.io/angular-viewchild/
- Angular 9/8 DOM Queries: ViewChild and ViewChildren Example: https://www.techiediaries.com/angular/angular-9-dom-queries-viewchild-viewchildren-example/
- Understanding ViewChildren, ContentChildren, and QueryList in Angular: https://netbasal.com/understanding-viewchildren-contentchildren-and-querylist-in-angular-896b0c689f6e
- How To Use ViewChild in Angular to Access a Child Component, Directive, or DOM Element: https://www.digitalocean.com/community/tutorials/angular-viewchild-access-component
- What are all the valid selectors for ViewChild and ContentChild?: https://stackoverflow.com/questions/49162473/what-are-all-the-valid-selectors-for-viewchild-and-contentchild
- Understanding ngTemplateOutlet, @ContentChild, ng-container in Reusable Component: https://www.hellojava.com/a/59883.html
- ViewChildren and ContentChildren in Angular : https://blog.mgechev.com/2016/01/23/angular2-viewchildren-contentchildren-difference-viewproviders/
- The Difference Between @ViewChildren and @ContentChildren: https://www.joshmorony.com/the-difference-between-viewchildren-and-contentchildren