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

Angular响应式表单操作模态框后预加载数据丢失报NG0100错误

问题场景

项目侧边导航栏的某个导航链接会携带输入数据,调用存在异常的业务组件:

  • 首次从侧边导航进入视图时,表单预加载数据可正常展示,组件运行正常
  • 点击按钮触发模态框、关闭模态框后,输入框数据暂时显示正常
  • 跳转侧边栏其他页面再返回当前页时,表单所有预加载数据全部消失,控制台抛出NG0100 表达式变更后被检查错误
  • 手动添加ngAfterViewChecked生命周期强制触发变更检测后,控制台报错消失,但预加载数据丢失的问题仍然存在
相关代码

业务组件TypeScript代码

@Input() data: MyData;
myModal: BsModalRef;
editForm: FormGroup;

ngOnInit(){
    this.editForm = this.formBuilder.group({
        inOne: [this.data.one, [Validators.required]],
        inTwo: [this.data.two, [Validators.required]]
    });
}

showModal() {
    const config: ModalOptions = {
        initialState: {
            message: 'Some dynamic tip message'
        }
    };
    this.myModal = this.modalService.show(MyModalComponent, config);
}

业务组件HTML模板代码

<form [formGroup]="editForm" (ngSubmit)="editForm.valid && save()">
    <h3>SIMPLE MODE</h3>
    <app-text-input 
        [formControl]="editForm.controls['inOne']" [type]="'number'">
    </app-text-input>
    <app-text-input 
        [formControl]="editForm.controls['inTwo']" [type]="'number'">
    </app-text-input>
    
    <button class="btn btn-info btn-sm" (click)="showModal()">Tip</button>
</form>

自定义通用输入组件app-text-input代码

<div class="form-group">
    <input 
        [class.is-invalid]='ngControl.touched && ngControl.invalid' 
        type={{type}} 
        class="form-control"
        [formControl]="ngControl.control" 
        placeholder={{label}}"
    >
    <!-- some validators -->
</div>

控制台报错信息

core.mjs:6485 ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for 'ng-valid': 'true'. Current value: 'false'.
    at throwErrorIfNoChangesMode (core.mjs:6733:1)
    at bindingUpdated (core.mjs:12710:1)
    at checkStylingProperty (core.mjs:16451:1)
    at ɵɵclassProp (core.mjs:16359:1)
    at NgControlStatus_HostBindings (forms.mjs:1367:1)
    at processHostBindingOpCodes (core.mjs:9251:1)
    at refreshView (core.mjs:9530:1)
    at refreshComponent (core.mjs:10655:1)
    at refreshChildComponents (core.mjs:9280:1)
    at refreshView (core.mjs:9534:1)

已尝试的无效修复

ngAfterViewChecked(): void {
    this.changeDetectorRef.detectChanges();
}
根本原因
  • 核心问题在自定义app-text-input组件的实现不符合Angular表单规范:组件没有正确注入NgControl、也没有实现自定义表单控件需要的ControlValueAccessor接口,直接在模板里硬绑定ngControl.control属于非法访问表单控件内部引用的写法,本身就存在变更检测不稳定的隐患。
  • 调用ngx-bootstrap的模态框服务时,模态框会启动独立的变更检测上下文,此时非法绑定的表单控件引用会被模态框的上下文污染;关闭模态框后,控件状态引用没有被正确释放,Angular在变更检测校验阶段发现单周期内ng-valid状态从true突变为false,直接抛出NG0100错误。
  • 之前添加的detectChanges()只是强制Angular接受了突变的状态,没有修复引用污染的根因。当路由跳走再返回时,业务组件会被重建,此时被污染的表单控件引用无法正确接收初始化传入的data参数值,控件初始值为空,必填校验直接失败,最终表现为预加载数据丢失。
  • 自定义输入组件模板里placeholder={{label}}"末尾多写了一个双引号,属于语法小错误,也会干扰模板解析稳定性。
修复方案
  1. 重构自定义app-text-input组件,按规范接入Angular表单体系

    • 首先在组件类的构造函数里注入NgControl,并指定valueAccessor指向当前组件实例:
    import { NgControl, ControlValueAccessor } from '@angular/forms';
    // ... 其他导入
    @Component({
        selector: 'app-text-input',
        templateUrl: './text-input.component.html'
        // 注意不要在providers里重复添加NG_VALUE_ACCESSOR,否则会造成循环依赖
    })
    export class TextInputComponent implements ControlValueAccessor {
        @Input() type: string = 'text';
        @Input() label: string = '';
        value: any;
        onChange = (value: any) => {};
        onTouched = () => {};
    
        constructor(@Self() public ngControl: NgControl) {
            this.ngControl.valueAccessor = this;
        }
    
        writeValue(value: any): void {
            this.value = value;
        }
    
        registerOnChange(fn: any): void {
            this.onChange = fn;
        }
    
        registerOnTouched(fn: any): void {
            this.onTouched = fn;
        }
    }
    
    • 修正组件模板,移除非法的[formControl]="ngControl.control"绑定,修复多余的引号语法错误:
    <div class="form-group">
        <input 
            [class.is-invalid]='ngControl.touched && ngControl.invalid' 
            [type]="type" 
            class="form-control"
            [value]="value"
            (input)="onChange($any($event.target).value)"
            (blur)="onTouched()"
            [placeholder]="label"
        >
        <!-- some validators -->
    </div>
    
  2. 移除之前在业务组件里加的ngAfterViewChecked中强制调用detectChanges()的代码,这种写法不仅会掩盖真实的变更检测异常,还会造成不必要的性能损耗。

  3. 监听模态框隐藏事件,在模态框关闭后主动标记组件状态为脏,清理模态框带来的变更检测上下文残留:

    showModal() {
        const config: ModalOptions = {
            initialState: {
                message: 'Some dynamic tip message'
            }
        };
        this.myModal = this.modalService.show(MyModalComponent, config);
        this.myModal.onHidden?.subscribe(() => {
            this.changeDetectorRef.markForCheck();
        });
    }
    
  4. (可选优化)将表单初始化逻辑调整到ngOnChanges生命周期中,监听输入参数data的变化,每次路由携带新参数进入组件时都能正确同步值到表单,避免路由复用场景下的数据不同步问题:

    ngOnChanges(changes: SimpleChanges) {
        if (changes['data'] && this.data) {
            if (!this.editForm) {
                this.editForm = this.formBuilder.group({
                    inOne: [this.data.one, [Validators.required]],
                    inTwo: [this.data.two, [Validators.required]]
                });
            } else {
                this.editForm.patchValue({
                    inOne: this.data.one,
                    inTwo: this.data.two
                });
            }
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 22:57:20