You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用OnPush变更检测策略时,模板中属性值为何不更新?

问题代码

Component.ts

@Component({
  selector: 'app-sample',
  templateUrl: './sample.component.html',
  styleUrls: ['./sample.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SampleComponent extends SortableComponent implements OnInit {
  public countNumbers: number = 0;
  public sampleItems;

  constructor(private sampleService: SampleService,) {
    super();
  }

  ngOnInit() {
    this.refreshList();
  }

  private refreshList() {
    this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad + 1;

    this.sampleService
      .getSample()
      .then(
        (response: any) => {
          this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
          this.sampleItems = response;
        },
        (reason: Error) => {
          this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
          console.log(reason);
        },
      );
  }
}

Template

<div class="col-sm pt-4" *ngIf="numberOfAssetsLeftToLoad !== 0">
      <loader loadingText="Loading Sample..." cssClass="w-50"></loader>
    </div>
    <div class="col-sm pt-4" *ngIf="numberOfAssetsLeftToLoad === 0">
      <div *ngFor="let item of sampleItems;">{{item}}</div>
    </div>

问题描述

启用ChangeDetectionStrategy.OnPush后,模板中的numberOfAssetsLeftToLoad始终显示为1,加载状态无法结束——TS文件中属性值已更新,但模板未同步变化。

原因分析

OnPush策略下,Angular仅在以下场景触发变更检测:

  • 组件@Input属性的引用发生变化
  • 组件/子组件触发DOM事件(如click、input)
  • 手动调用变更检测方法
  • 通过async管道订阅的Observable流发出新值

而代码中Promise的回调属于异步操作,Angular不会自动为其触发变更检测,导致模板无法感知到numberOfAssetsLeftToLoad和sampleItems的更新。

解决方法

方法1:手动触发变更检测

注入ChangeDetectorRef,在Promise回调修改属性后主动通知Angular检测变更:

import { ChangeDetectorRef } from '@angular/core';

// ...

constructor(
  private sampleService: SampleService,
  private cdr: ChangeDetectorRef // 注入变更检测引用
) {
  super();
}

private refreshList() {
  this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad + 1;

  this.sampleService
    .getSample()
    .then(
      (response: any) => {
        this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
        this.sampleItems = response;
        this.cdr.detectChanges(); // 触发变更检测
      },
      (reason: Error) => {
        this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
        console.log(reason);
        this.cdr.detectChanges(); // 错误分支同步触发
      },
    );
}

方法2:改用Observable + async管道(推荐)

将Promise转为Observable,利用async管道自动处理变更检测,同时把加载状态封装为可观察对象:

import { BehaviorSubject } from 'rxjs';
import { finalize } from 'rxjs/operators';

// ...

export class SampleComponent extends SortableComponent implements OnInit {
  public countNumbers: number = 0;
  public sampleItems;
  public isLoading$ = new BehaviorSubject(true);

  constructor(private sampleService: SampleService,) {
    super();
  }

  ngOnInit() {
    this.refreshList();
  }

  private refreshList() {
    this.isLoading$.next(true);
    this.sampleService
      .getSample()
      .pipe(
        finalize(() => this.isLoading$.next(false)) // 请求结束后标记加载完成
      )
      .subscribe(
        (response: any) => {
          this.sampleItems = response;
        },
        (reason: Error) => {
          console.log(reason);
        },
      );
  }
}

模板同步修改:

<div class="col-sm pt-4" *ngIf="isLoading$ | async">
  <loader loadingText="Loading Sample..." cssClass="w-50"></loader>
</div>
<div class="col-sm pt-4" *ngIf="!(isLoading$ | async)">
  <div *ngFor="let item of sampleItems;">{{item}}</div>
</div>

方法3:用NgZone包裹Promise回调

Angular变更检测运行在NgZone环境内,手动将Promise回调包裹到NgZone中,让Angular感知到异步操作:

import { NgZone } from '@angular/core';

// ...

constructor(
  private sampleService: SampleService,
  private ngZone: NgZone
) {
  super();
}

private refreshList() {
  this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad + 1;

  this.sampleService
    .getSample()
    .then(
      (response: any) => {
        this.ngZone.run(() => {
          this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
          this.sampleItems = response;
        });
      },
      (reason: Error) => {
        this.ngZone.run(() => {
          this.numberOfAssetsLeftToLoad = this.numberOfAssetsLeftToLoad - 1;
        });
        console.log(reason);
      },
    );
}

内容的提问来源于stack exchange,提问作者Narek Keshishyan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 12:54:25