[Angular] ViewChild annotation returns undefined

While working on developing my own Web app, I got an issue with ViewChild annotation that always returns an object of undefined. I did an investigation and found out the problem is that I used ngIf directive within the component. Let’s see an example below and we are going to deeply dive into this one.

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <div>
          <input [(ngModel)]="inputValue" />
      </div>
      <div *ngIf="inputValue">
          <button #myButton>Click me</button>
      </div>
    </div>
  `
})

export class MyComponentComponent implements AfterViewInit {
  @ViewChild('myButton') button: ElementRef;

  constructor() { }

  ngAfterViewInit(): void {
    console.log(this.button);
    /*
    Result: undefined
    */
  }
}

Suppose I have an input field, and a button will be hidden by default. The button will only appear when the user types something in the text box to allow the user to click on it. In reality, we’re going to do other stuff. But in the case of this example, I’m using console.log(); to see the value of the element since I just wanted to show you the issue between ViewChild annotation and ngIf directive what I’m talking about. And the result of this console.log(this.button); is clearly undefined because the button element will not be created if the inputValue is null, it’s because ngIf directive will shape or reshape the DOM’s structure by adding or removing elements. And in this case, it’s not added yet. That’s why we got an object of undefined of the ViewChild.

So how will we fix that? We have two solutions for this one. Using hidden or style.display attribute.

Solution #1: Hidden attribute.

Instead of using ngIf directive, we now switch to use hidden attribute. Update the HTML code in the template, it is going to be changed like this. Since using the hidden attribute, the element is still generated in the DOM, and it’s just hidden from the UI so that the user will not see it.

<div>
    <div>
        <input [(ngModel)]="inputValue" />
    </div>
    <div [hidden]="!inputValue">
        <button #myButton>Click me</button>
    </div>
</div>

Solution #2: Style.display attribute

We’re going to bind display method to style.display attribute. In the class of ts file, display method will return a value of block or none that depends on inputValue. And the user will not see the button if display method returns block.

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-my-component',
  template: `
    <div>
      <div>
          <input [(ngModel)]="inputValue" />
      </div>
      <div [style.display]="display()">
          <button #myButton>Click me</button>
      </div>
    </div>
  `
})

export class MyComponentComponent implements AfterViewInit {
  inputValue: string;
  @ViewChild('myButton') button: ElementRef;

  constructor() { }

  ngAfterViewInit(): void {
    console.log(this.button);
    /*
    Result: undefined
    */    
  }

  display(): string {
    if (this.inputValue) {
      return 'block';
    } else {
      return 'none';
    }
  }
}

Conclusions

Since the ngIf won’t add the element in if the expression is false. So the two solutions above are just to hide button from the UI in order for us to be able to get the Element from ViewChild.
Hope this helps you guys who have the same issue. If you have any questions or other workarounds, please add it to comment below.

2 Comments [Angular] ViewChild annotation returns undefined

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.