1

Instead of copy-pasting my codes, let me share the code in stackblitz. The two components in action are:

  1. SearchResultComponent
  2. SearchResultTableComponent

The code that I am concerned with is the template file search-result.component.html, and here is the content:

<div class="content-container">
  <button mat-raised-button color="primary" (click)="onLoadClick()">Load</button>
  <button mat-raised-button color="primary" class="nop-button">NOP button</button>

  <div class="table-container">
    <!-- Does not work -->
    <app-search-result-table *ngIf="searchResult.length" [searchResult]="searchResult"></app-search-result-table>

    <!-- Works -->
    <!-- <app-search-result-table [hidden]="!searchResult.length" [searchResult]="searchResult"></app-search-result-table> -->
  </div>
</div>

What I intend to do is hide the table when the searchResult is empty (initially it is indeed empty), and show it only when it has at least one data row (clicking the "Load" button does that). So I expect it to work by using *ngIf="searchResult.length" but it does not work as expected. Instead what happens is, when I click the "Load" button the first time, it shows the table headers but no data row is displayed (in my actual app, the data on clicking "Load" comes from API, but for demo I am randomly taking elements from an array, and making an observable using the of operator). The data rows however show up when any event occurs that triggers a view update to be triggered - clicking anywhere else in the page or clicking the "NOP button" (it actually is a no-op button) causes the table to show the data rows. Subsequent clicking of the "Load" button causes it to work as expected. But, when I use the hidden directive it always works as expected, including the first time. The line is there in my template file and you can uncomment it (and comment out the ngIf line) and try it out in stackblitz demo I linked.

I know the difference between ngIf and hidden directive. This article explains it in great detail. I also studied this SO post but my situation is different from that one.

What is perplexing to me is why does the table only show the header rows when I click the "Load" button the first time, and then full table is displayed on the next DOM update when using the *ngIf approach?

Sнаđошƒаӽ
  • 16,753
  • 12
  • 73
  • 90
  • 1
    It’s actually expected behaviour and probably caused by the way you ‘read’ the input on the search-result-table component. It’s basically ‘missing’ the input because the data update probably has already happened once it’s created. That is why the hidden works - as that just hides the component (but does create it). I haven’t looked at your stackblitz (on my phone at the mo) - but it’s probably solvable inside the child by updating stuff inside ngOnChanges. – MikeOne Aug 04 '20 at 19:34

1 Answers1

2

You are not using the dataSource input on the mat-table, so no change detection is triggered for the table. You can change your search-result-table to:

@Component({
  selector: 'app-search-result-table',
  templateUrl: './search-result-table.component.html',
  styleUrls: ['./search-result-table.component.css']
})
export class SearchResultTableComponent implements OnChanges {
  @Input() searchResult: UserInfo[] = [];

  readonly dataSource = new MatTableDataSource<UserInfo>([]);
  
  columnNames: string[] = ['firstName', 'lastName', 'email'];

  ngOnChanges(changes: SimpleChanges) {
    if (changes.searchResult) {
      this.dataSource.data = this.searchResult;
    }
  }
}

with as template:

<table mat-table class="mat-elevation-z8" [dataSource]="dataSource">
....

working example

Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • Thanks, but could you please explain why it was showing only the header rows on the first Load click? – Sнаđошƒаӽ Aug 04 '20 at 19:50
  • @Sнаđошƒаӽ because `*ngIf="searchResult.length"` resolved to true after the `setTimeout`, so the table is shown. Just the data is not set at the right time in the change detection cycle to actually show up. That's why you have to use the `dataSource` input on the mat-table to assure this – Poul Kruijt Aug 05 '20 at 06:18
  • I am setting the `dataSource` too, in the `ngAfterViewInit()`. Also, apart from the first "Load" click, the table is populated as expected for the subsequent "Load" clicks. So not sure why/how data is not set at the right time in the change detection cycle. I would appreciate any further explanation. – Sнаđошƒаӽ Aug 05 '20 at 13:17
  • 1
    @Sнаđошƒаӽ the `ngAfterViewInit` hook does not trigger a change detection cycle by itself. So anything what happens there is not picked up with the current cycle, and will be picked up with the consecutive one. If you move it to `ngOnInit` and set the `{ static: true }` on the `@ViewChild` you will see that it works as well. – Poul Kruijt Aug 05 '20 at 14:45