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

为何正确配置仍出现'expression has changed after it was checked'错误?

Angular中expression has changed after it was checked错误原因解析

我遇到了expression has changed after it was checked错误,已知该检查仅在dev模式下运行(Angular会执行两次变更检测),生产环境不会出现此报错,但想了解报错原因。

实现的核心代码

基于Signal的LoaderService

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class LoaderService {
  private _isLoading = signal<boolean>(false);

  get isLoading(): boolean {
    return this._isLoading();
  }

  show() {
    this._isLoading.update(() => true);
  }

  hide() {
    this._isLoading.update(() => false);
  }
}

HTTP拦截器配置

import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpResponse,
} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, catchError, map, throwError } from 'rxjs';
import { USE_LOADING_SPINNER } from '../constants/app.constants';
import { LoaderService } from '../../shared/services/loader.service';

@Injectable()
export class LoadingInterceptor implements HttpInterceptor {
  constructor(private loaderService: LoaderService) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.context.get(USE_LOADING_SPINNER)) this.loaderService.show();

    return next.handle(req).pipe(
      map((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) this.loaderService.hide();
        return event;
      }),
      catchError((error) => {
        this.loaderService.hide();
        return throwError(() => error);
      })
    );
  }
}

USE_LOADING_SPINNER默认设为true,仅需标记不需要加载指示器的请求。

组件中的使用方式

组件构造函数中通过effect同步Signal的值到普通布尔变量:

constructor() {
    effect(() => {
      this.loading = this.loaderService.isLoading;
    });
  }

loading是普通布尔变量,模板中网格组件绑定了[loading]="loading"。

问题触发场景

初始路由加载时,加载指示器正常显示;但添加如下刷新方法后:

refresh() {
    //this.loaderService.show();
    this.gridData$ = this.userService.getUsers();
    //.pipe(tap(() => this.loaderService.hide()));
  }

点击刷新会触发expression has changed after it was checked错误,且加载指示器不显示(已确认拦截器被触发);取消注释手动调用loaderService的代码后,功能恢复正常。

错误原因分析

  1. Dev模式的双次变更检测机制
    Angular在开发模式下会执行两次变更检测:第一次完成后,会对比所有绑定的值,如果第二次检测时发现值和第一次不一致,就会抛出该错误,目的是提前发现可能的变更检测不一致问题。

  2. Signal更新与变更检测的时序冲突

  • 当调用this.gridData$ = this.userService.getUsers();时,HTTP请求发起,拦截器调用loaderService.show(),此时Signal的_isLoading更新为true。
  • 组件中的effect默认在变更检测周期结束后执行:第一次变更检测时,loading变量还是初始的false;effect运行后将其改为true,导致第二次变更检测时发现值不一致,触发错误。
  • 同时,由于loading的更新发生在变更检测之后,模板绑定未及时同步,所以加载指示器不显示。
  1. 手动调用正常的原因
    手动调用this.loaderService.show()是在变更检测周期内执行的:点击事件触发refresh方法,属于Angular的事件处理流程,会触发变更检测,此时loading变量的更新会被纳入第一次变更检测,两次检测的值一致,不会报错,模板也能及时更新状态。

优化建议

  • 将组件的loading改为Signal,直接在模板中绑定[loading]="loaderService.isLoading()",让Angular自动追踪Signal变化并触发变更检测,避免手动同步的问题。
  • 若需保留普通变量,可在effect中注入ChangeDetectorRef,调用cdr.detectChanges()手动触发变更检测,但这种方式不如直接使用Signal绑定优雅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 09:07:05