Search code examples
javascriptangulartypescriptrxjsngrx

viewChild inside *ngIf resulting undefined reference


I would like to make a loading When requesting data from ngrx+ api http request

I have a state that contain loading boolean, and another with raw data.

I set loading to false when data arrive.

the problem viewChild don't find reference because data arrive before loading set to false.

I get error

ERROR TypeError: Cannot read property 'nativeElement' of undefined

Here's the template

<div *ngIf="isLoading; else kpi"><mat-spinner></mat-spinner></div>
<ng-template #kpi>
  <div class="chartWrapper">
    <div class="scrollArea">
      <div class="chartAreaWrapper">
        <canvas #lineChart id="lineChart" width="1200" height="400"></canvas>
      </div>
    </div>
    <canvas #stickyAxis id="chartAxis" height="400" width="0"></canvas>
  </div>
</ng-template>

in component

export class BranchKpisComponent implements OnChanges, OnInit, OnDestroy {
  @ViewChild('lineChart', { static: true }) private chartRef;

I'm using ngrx store

I have the selectors

selectLoadings$ = this.store.pipe(select(selectLoadings));
selectedDataByBranch$ = this.store.pipe(
 select(selectBranchDirections, {
   branchId: this.branchId,
   location: 'directionsByBranch',
   dir: 0
 })

inside ngOnchange() I subscribe to loading and data observable ( separately )

this.selectLoadings$.subscribe(
  loading => (this.isLoading = loading.directionsByBranch[this.branchId])
);

this.selectedDataByBranch$
      .pipe(filter(data => Object.keys(data).length > 0))
      .subscribe(selectedDataByBranch => {
        this.trainsDatasets = this.getDatasets(selectedDataByBranch);
        this.context = this.chartRef.nativeElement; ### Error undefined chartRef

Inside reducer when I get data I set loading to false

case ESchedulesActions.GetAllSchedulesByDirectionSuccess: {
  return {
    ...state,
    directionsByBranch: {
      ...state.directionsByBranch,
      [action.payload[1]]: action.payload[0]
    },
    loadings: {
      ...state.loadings,
      directionsByBranch: {
        ...state.loadings.directionsByBranch,
        [action.payload[1]]: false
      }
    }
  };
}

Solution

  • You could use [hidden] instead of *ngIf. It will still have the element you want to view in the DOM. Would need another one instead of the else which is the opposite.

    <div [hidden]="!isLoading"><mat-spinner></mat-spinner></div>
    <ng-template [hidden]="isLoading">
      <div class="chartWrapper">
        <div class="scrollArea">
          <div class="chartAreaWrapper">
            <canvas #lineChart id="lineChart" width="1200" height="400"></canvas>
          </div>
        </div>
        <canvas #stickyAxis id="chartAxis" height="400" width="0"></canvas>
      </div>
    </ng-template>
    

    In terms of performance there won't be much difference if at all due to the size of the code.