【问题标题】:viewChild inside *ngIf resulting undefined reference [duplicate]*ngIf 内的 viewChild 导致未定义的引用 [重复]
【发布时间】:2020-05-04 04:57:35
【问题描述】:

从ngrx+ api http请求请求数据时我想做一个加载

我有一个包含加载布尔值的状态,另一个包含原始数据。

当数据到达时,我将 loading 设置为 false。

问题viewChild 找不到参考,因为数据在加载之前到达设置为假。

我得到错误

ERROR TypeError: Cannot read property 'nativeElement' of undefined

这是模板

<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>

在组件中

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

我正在使用 ngrx 商店

我有选择器

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

ngOnchange()里面我订阅了loading和data observable(分开)

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

当我获取数据时在减速器内部我将加载设置为 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
      }
    }
  };
}

【问题讨论】:

  • 它本身说 chartRef 是未定义的。你有没有添加任何参考??
  • 是的,我已经更新了答案
  • 问题在于将 loading 更改为 false 只会在当前事件处理阶段完成后更新子视图。最简单的技巧是在使用引用的代码之前添加一个 setTimeout。更好的解决方案是不使用视图子组件并通过 HTML 进行通信,以便 Angular 在渲染时设置您的输入,而不是从控制器/组件中进行

标签: javascript angular typescript rxjs ngrx


【解决方案1】:

您可以使用 [hidden] 代替 *ngIf。它仍然会有你想要在 DOM 中查看的元素。需要另一个而不是相反的 else。

<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>

就性能而言,由于代码的大小,根本不会有太大差异。

【讨论】:

  • 这是一种方法,但它确实有在不需要时实例化部分 DOM 的缺点。当您渲染大量已初始化且属于变更检测的组件时,这可能会出现问题。
  • 查看标记为已接受的答案以获得更好的选择
【解决方案2】:

viewChild 应该这样实现。综上所述,我认为您以错误的方式实现了 viewChild。

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! ??";
  }
}

所以你更新的代码块将是

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

别忘了导入ElementRef

【讨论】:

  • 你没有解释问题是什么,我不认为这会解决问题
  • @JuanMendes 我认为 viewChild 的实现似乎是错误的,所以这里的 chartRef 是未定义的。这就是我从他发布的区块中得到的。
  • 我认为这是因为ngIf 条件,即使我使用ngafterInitView 引用还不存在,因为条件为假所以引用div 尚未在DOM 上,我会能够在数据来自API时获取引用,然后更新dom,此时我可以获取引用,但我不知道如何
  • @infodev 正如我在评论中解释的那样。
  • @AkhilAravind 这只是未定义的,因为他在更改重新渲染模板的标志时正在检查它。您必须等到模板再次呈现。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-05
  • 2017-01-14
  • 2020-01-21
  • 2018-02-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多