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.