angularangular2-changedetectionviewchild

@ViewChild in *ngIf


Question

What is the most elegant way to get @ViewChild after corresponding element in template was shown?

Below is an example. Also Plunker available.

Component.template.html:

<div id="layout" *ngIf="display">
  <div #contentPlaceholder></div>
</div>

Component.component.ts:

export class AppComponent {

    display = false;
    @ViewChild('contentPlaceholder', { read: ViewContainerRef }) viewContainerRef;

    show() {
        this.display = true;
        console.log(this.viewContainerRef); // undefined
        setTimeout(() => {
            console.log(this.viewContainerRef); // OK
        }, 1);
    }
}

I have a component with its contents hidden by default. When someone calls show() method it becomes visible. However, before Angular 2 change detection completes, I can not reference to viewContainerRef. I usually wrap all required actions into setTimeout(()=>{},1) as shown above. Is there a more correct way?

I know there is an option with ngAfterViewChecked, but it causes too much useless calls.

ANSWER (Plunker)


Solution

  • Use a setter for the ViewChild:

     private contentPlaceholder: ElementRef;
    
     @ViewChild('contentPlaceholder') set content(content: ElementRef) {
        if(content) { // initially setter gets called with undefined
            this.contentPlaceholder = content;
        }
     }
    

    The setter is called with an element reference once *ngIf becomes true.

    Note, for Angular 8 you have to make sure to set { static: false }, which is a default setting in other Angular versions:

     @ViewChild('contentPlaceholder', { static: false })
    

    Note: if contentPlaceholder is a component you can change ElementRef to your component Class:

      private contentPlaceholder: MyCustomComponent;
    
      @ViewChild('contentPlaceholder') set content(content: MyCustomComponent) {
         if(content) { // initially setter gets called with undefined
              this.contentPlaceholder = content;
         }
      }