Here is the template of main.html
:
<button (click)="displayAndUseMyCustomComp()"></button>
<my-custom-comp *ngIf="isMyCustomCompDisplayed" #myCustomComp="myCustomComp"></my-custom-comp>
and main.component.ts
:
export class MainComponent {
constructor() {}
private isMyCustomCompDisplayed boolean = false
@ViewChild('myCustomComp') myCustomComp: MyCustomComp
displayAndUseMyCustomComp() {
this.isMyCustomCompDisplayed = true
console.log(this.myCustomComp) // prints undefined
setTimeout(() => {
console.log(this.myCustomComp) // prints {object}
})
}
}
What's happening is that my template isn't yet refreshed after I set isMyCustomCompDisplayed
to true
. However, if I use a setTimeout
, myCustomComp
gets updated and my issue goes away. It is midly hacky, and I was wondering what was the correct way of doing what I am trying to.
The ViewChild decorator returns the first element that matches a given directive, component, or template reference selector.
Conclusion. The @ViewChild decorator allows us to inject into a component class references to elements used inside its template, that's what we should use it for. Using @ViewChild we can easily inject components, directives or plain DOM elements.
ViewChild is used to select an element from component's template while ContentChild is used to select projected content.
However if we change static to false, then instead we get this : undefined ChildComponent. Because ngAfterViewInit runs after the first ChangeDetection cycle, our component is available, and because setting static to false means after every ChangeDetection, we re-query, we are able to find our ChildComponent.
displayAndUseMyCustomComp
Angular updates ViewChild
query list as part of change detection. When Angular was running initial change detection the isMyCustomCompDisplayed
was false
and so myCustomComp
was hidden. The myCustomComp
was set to undefined
.
After you make a click the function displayAndUseMyCustomComp
is executed and isMyCustomCompDisplayed
is set to true
. Angular requires a change detection cycle to update the myCustomComp
query list. However, you try to read the value immediately and so it's still undefined
. You need to wait for another change detection cycle for Angular to update the query list.
setTimeout
If you try to read the myCustomComp
inside the timeout, Angular has a chance to run change detection between the update to isMyCustomCompDisplayed
and the time you read myCustomComp
.
When Angular runs change detection for the MainComponent
it detects that isMyCustomCompDisplayed
is updated. So it goes and updates bindings for ngIf
. It in turn reacts to this change and creates and embedded view with the myCustomComp
and attaches it to the MainComponent
component:
@Input()
set ngIf(condition: any) {
if (condidition) {
this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
If you're looking for synchronous solution, it will be available inside all lifecycle hooks that are executed after the view children query list is updated during the next change detection cycle that follows execution of displayAndUseMyCustomComp
. At the moment these are ngAfterViewInit
and ngAfterViewChecked
. Since the former is called only once, we need to use ngAfterViewChecked
:
ngAfterViewChecked() {
if(this.isMyCustomCompDisplayed) {
console.log(this.myCustomComp) // prints {object}
}
}
displayAndUseMyCustomComp() {
this.isMyCustomCompDisplayed = true
}
Another synchronous solution suggested by @ThinkingMedia
is also good. You can use ViewChildren
instead of ViewChild
and subscribe to changes
(btw you don't template reference):
@ViewChildren(myCustomComp) as: QueryList<myCustomComp>;
ngAfterViewInit() {
this.myCustomComp.changes.subscribe(() => {
console.log(this.myCustomComp.first); // prints {object}
});
}
The callback will be triggered during next digest when Angular will be updating query list (slightly earlier than ngAfterViewChecked
).
If you're looking for asynchronous solution, use setTimeout
as you do it. The Promise.resolve(null).then(()=>{ console.log(this.myCustomComp) })
(microtask) solution won't work because it will be executed after the current stack but before the change detection.
For more information on change detection read
Everything you need to know about change detection in Angular.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With