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

Angular响应式表单使用patchValue更新禁用状态失效及报错问题

问题修复方案

问题根因

  • ExpressionChangedAfterItHasBeenCheckedError报错:切换toggle按钮时,你在同一个变更检测周期内同步修改了表单校验规则,直接触发了表单valid状态变更,导致提交按钮的disabled属性在Angular开发环境的两次变更检测校验中值不一致,因此抛出该错误。
  • 切回Email模式按钮仍禁用:Angular响应式表单调用setValidators/clearValidators修改校验规则后,不会自动重新计算控件和父表单的校验状态,必须手动触发状态更新,你未执行该操作导致表单状态没有同步刷新。

修复步骤

1. 修改app.component.ts的初始化逻辑

表单初始化完成后主动触发一次校验规则设置,给默认选中的Email模式加上对应校验:

import { Component, OnInit, VERSION } from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  name = 'Angular ' + VERSION.major;
  DELIVERY_MODES = {
    sms: 'sms',
    email: 'email'
  };
  deliveryForm: FormGroup;

  constructor(private formBuilder: FormBuilder) {}

  ngOnInit(): void {
    const dummyData = new PlatformEnrollmentDelivery();
    dummyData.hour = 10;
    dummyData.minute = 30;
    dummyData.modeTarget = "example@domain.com"
    this.deliveryForm = this.formBuilder.group({
      deliveryConfiguration: this.initDeliveryModeFormGroup(dummyData),
      setDefaults: this.formBuilder.group({
        setDefault: false
      })
    });
    // 新增:初始化默认校验规则
    this.setDeliveryMethodType(this.DELIVERY_MODES.email);
  }

  // 其余原有初始化方法保持不变
  initDeliveryModeFormGroup(delivery: PlatformEnrollmentDelivery): any {
    const defaultMode = delivery.mode ? delivery.mode.toLowerCase() : this.DELIVERY_MODES.email;
    return this.formBuilder.group({
      mode: [defaultMode],
      email: this.formBuilder.group(this.initEmailDeliveryMode(delivery)),
      sms: this.formBuilder.group(this.initSmsDeliveryMode(delivery)),
      deliveryTime: this.formBuilder.group(this.initDeliveryTime(delivery))
    });
  }

  initEmailDeliveryMode(delivery: PlatformEnrollmentDelivery): object {
    const emailControlValue = delivery.mode.toLowerCase() === this.DELIVERY_MODES.email ? delivery.modeTarget : '';
    return {
      emailControl: [emailControlValue]
    };
  }

  initSmsDeliveryMode(delivery: PlatformEnrollmentDelivery): object {
    const smsControlValue = delivery.mode.toLowerCase() === this.DELIVERY_MODES.sms ? delivery.modeTarget : '';
    return {
      smsControl: [smsControlValue]
    };
  }

  initDeliveryTime(delivery: PlatformEnrollmentDelivery): object {
    return {
      timeControl: ["10:00"]
    };
  }

2. 重写setDeliveryMethodType方法

把校验规则修改逻辑放到微任务队列避开当前变更检测周期,同时手动触发控件校验状态更新:

setDeliveryMethodType(type: string): void {
  this.deliveryForm.controls.deliveryConfiguration.get('mode').patchValue(type);
  // 放到微任务中执行,规避同周期状态变更报错
  Promise.resolve().then(() => {
    const emailControl = this.deliveryForm.controls.deliveryConfiguration.get('email').get('emailControl');
    const smsControl = this.deliveryForm.controls.deliveryConfiguration.get('sms').get('smsControl');
    
    switch (type) {
      case this.DELIVERY_MODES.email:
        emailControl.setValidators([Validators.required, Validators.email]);
        smsControl.clearValidators();
        break;
      case this.DELIVERY_MODES.sms:
        smsControl.setValidators([Validators.required]);
        emailControl.clearValidators();
        break;
    }

    // 手动触发控件校验状态更新,同步到父表单
    emailControl.updateValueAndValidity();
    smsControl.updateValueAndValidity();
  })
}
}

export class PlatformEnrollmentDelivery {
  id: number;
  mode: string;
  hour: number;
  updated: Date;
  minute: number
  modeTarget: string;

  constructor(
  ) {
      this.mode = "email";
  }
}

可选替代方案

如果你不想用微任务的写法,也可以通过注入ChangeDetectorRef手动触发变更检测解决报错,两种方案效果一致:

  1. 构造器注入ChangeDetectorRef:
constructor(private formBuilder: FormBuilder, private cdr: ChangeDetectorRef) {}
  1. 在setDeliveryMethodType方法末尾调用this.cdr.detectChanges()即可。

app.component.html代码无需任何修改,修改完成后两个问题都可以解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 17:09:03