2

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
      }
    }
  };
}
Akhil Aravind
  • 5,741
  • 16
  • 35
infodev
  • 4,673
  • 17
  • 65
  • 138
  • it itself says chartRef is undefined. Did you add any reference ?? – Akhil Aravind Jan 17 '20 at 10:11
  • yes I have Updated my answer – infodev Jan 17 '20 at 10:15
  • The problem is that changing loading to false only updates the view child after the current event handling phase finishes. The simplest hack is to add a setTimeout before the code that is using the reference. The better solution is not to have a view child component and communicate thorough the HTML instead so that angular will set your inputs when rendering it, instead of doing it from the controller/component – Ruan Mendes Jan 17 '20 at 10:28

2 Answers2

4

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.

ajrthegreat
  • 282
  • 1
  • 6
  • 1
    This is one way to do it, but it does have the drawback of instantiating part of the DOM when you don't need it. This can be problematic when you're rendering a lot of components that get initialized and are part of change detection. – Ruan Mendes Jan 17 '20 at 10:57
  • See the answer marked as accepted for a better option – Ruan Mendes Jan 17 '20 at 11:27
0

viewChild should be implemented in this way. From the above, I think you implemented viewChild in wrong way.

import { Component,
         ViewChild,
         AfterViewInit,
         ElementRef } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('someInput') someInput: ElementRef;

  ngAfterViewInit() {
    this.someInput.nativeElement.value = "Anchovies! ";
  }
}

so your updated code block will be

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

Dont forget to import ElementRef

Akhil Aravind
  • 5,741
  • 16
  • 35
  • You are not explaining what the problem was, and I dunt think this will solve the problem – Ruan Mendes Jan 17 '20 at 10:24
  • @JuanMendes i think it seems like viewChild is implemented is wrong, so the chartRef here is undefined. thats what i got from the block he posted. – Akhil Aravind Jan 17 '20 at 10:27
  • 1
    I think it's because of the `ngIf` condition ,even I use `ngafterInitView` the reference does not exist yet because the condition is false so the ref `div` is not yet on DOM, I would be able to get the reference when data came from API, then update the dom and at this time I could get the reference, But I don't know how – infodev Jan 17 '20 at 10:27
  • @infodev As I explained in my comment. – Ruan Mendes Jan 17 '20 at 10:55
  • 2
    @AkhilAravind It's only undefined because he's checking for it while changing the flag that re-renders the template. You have to wait until the template renders again. – Ruan Mendes Jan 17 '20 at 11:00
  • Hey to fix this problem use @ViewChild('lineChart', { static: false }) chartRef:ElementRef; An explanation of why is here, https://angular.io/guide/static-query-migration, but basically when static it true means the child component is not inside any structural directive like ngIf or ngFor, so it can be found faster and is available on ngInit, when static is false is the opposite is inside a directive like ngIf or similar structural directive so angular can only find it after a ngAfterViewInit when the conditions of ngIf etc have been evaluated – Gabriel Guerrero Jan 18 '20 at 22:14