Angular ViewChild
We have created a message component which displays some default validation messages. The following block is to display the default message, only if there is no other custom validation message defined.
<div *ngIf="!formControl">
<div #wrapper>
<ng-content select=".customSelector"></ng-content>
</div>
<ng-container *ngIf="!showDefaultMessage">
<messages></messages>
</ng-container>
</div>
Outer div has an ngIf
so that if there is no form control passed, the entire div is not going to be displayed. Inside the div, there are two parts. First part is the custom validation message and second is the default message. If there is custom validation message passed in, default message should not be shown. The following shows the component code.
public formControl = new FormControl();
/**
* Access wrapper element
*/
@ViewChild('wrapper', {static: true})
public wrapperElement!: ElementRef;
public ngAfterViewInit(): void {
this.showDefaultMessage = this.wrapperElement
&& this.wrapperElement.nativeElement
&& this.wrapperElement.nativeElement.textContent;
this.changeDetectorRef.detectChanges();
}
Once this code runs, it turns out that when there is custom validation message passed in, during the first change happens, both custom and default messages show up. Upon examing the wrapperElement
inside ngAfterContentChecked
, it shows that this.wrapperElement
is null
. Had made sure that outer div ngIf
condition is true
, so that wrapperElement
should be set up by the time ngAfterContentChecked
invoked. Why does it not show up? Also found out that if the outer ngIf
is removed, then this.wrapperElement
shows up correctly.
The problem lies in how we set up ViewChild
. In Angular 8, it's required to pass in static
flag, specifying it being true
or false
. So when it should be true
and when it should be false
? From the docs, it shows that
{ static: true } is to resolve query results before change detection runs, i.e. when the view element to be ready in ngOnInit.
{ static: false } is to resolve after change detection. So it's viewable after ngAfterViewInit triggered. This is also what you want to do for when you have a structural directive (*ngIf etc.) in your template.
There, we found our problem. Since we have *ngIf
structual directive, we should NOT set static
flag to be true
. And if we do that, we got the element not being defined.
NOTE: In Angular 9.x, the default of ViewChild
static flag is false
.