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.
thank you so much , it really helps me to understand the error.
thank you , it really helps a lot.