Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular4 How to know when a ViewChild has been reset

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.

like image 404
Scipion Avatar asked Jul 06 '17 11:07

Scipion


People also ask

What does ViewChild return?

The ViewChild decorator returns the first element that matches a given directive, component, or template reference selector.

Should you use ViewChild?

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.

What is the difference between ViewChild () and ContentChild ()?

ViewChild is used to select an element from component's template while ContentChild is used to select projected content.

What is static false in ViewChild?

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.


1 Answers

Here is why it's undefined after you call 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.

Here is why it's working if you wrap the call into 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.

Here is what happens during that change detection cycle

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);

When is the updated query list available

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.

like image 152
Max Koretskyi Avatar answered Oct 04 '22 15:10

Max Koretskyi