Angular 6切换问卷时如何完全重载Component解决表单残留值与报错问题
Angular 6 下实现组件重载的实操方案
问题根源
Angular的变更检测机制默认会复用DOM中同类型的组件实例,只要组件对应的*ngIf判断条件保持为真、组件选择器匹配,就算传入的输入属性发生变更,也不会重新创建组件实例。这就是切换QuestionIndex后,组件内表单仍保留上次填写内容的核心原因。
你之前使用ngOnChanges重置时出现循环触发、以及ExpressionChangedAfterItHasBeenCheckedError的问题,本质是两个错误写法导致:
- 在重置逻辑中修改了被
@Input装饰的传入属性,导致属性变更再次触发ngOnChanges形成闭环 - 在变更检测周期中途修改模板绑定值,触发了Angular的双向绑定校验报错
推荐实现方案(按优先级排序)
方案1:针对性重置内部状态(性能最优,无实例销毁开销)
这个方案不需要频繁销毁重建组件,也不会触发循环和报错,只要规避之前的写法误区即可:
- 给子组件(
app-radio-question)传入一个唯一标识当前问题的输入属性,比如questionId(对应currentQuestion的唯一id) - 在子组件中监听这个唯一标识的变化,只在标识真正变更时,重置组件内部的非输入属性(也就是表单实例),绝对不要在这个流程中修改任何被
@Input装饰的传入值,从根源上避免循环触发 - 示例子组件代码:
@Input() questionId: number; private lastResetQuestionId: number; form: FormGroup; ngOnInit() { // 把表单初始化逻辑独立封装成方法,方便复用 this.initForm(); } ngOnChanges(changes) { // 只监听questionId的变化,不修改任何@Input属性 if (changes.questionId && changes.questionId.currentValue !== this.lastResetQuestionId) { this.lastResetQuestionId = changes.questionId.currentValue; // 重新初始化整个表单,完全清空之前的填写状态 this.initForm(); } } private initForm() { this.form = new FormGroup({ response: new FormControl(null) }); } - 父组件调用代码只需要在原有基础上新增
questionId绑定即可:<app-radio-question [text]="currentQuestion.text" *ngIf="currentQuestion.type == 'RadioQuestion' && !showEndQuestion" [title]="currentQuestion.title" [NoOptionText]="currentQuestion.noOption" [YesOptionText]="currentQuestion.yesOption" [cacheData]="cacheData" [questionId]="currentQuestion.id" (formChangedEvent)="OnQuestionResponse($event)"> </app-radio-question> - 优势:没有组件销毁重建的性能开销,所有状态修改都在Angular变更检测的安全时机完成,不会出现循环触发,也不会报
ExpressionChangedAfterItHasBeenCheckedError。
方案2:强制完全重建组件实例(适合内部状态复杂的场景,代码改动最少)
如果组件内部状态非常多,逐个编写重置逻辑成本太高,可以直接让Angular完全销毁旧实例、创建全新实例,根本不需要写任何重置代码:
- 在父组件的调用中,利用DOM diff的节点识别特性,给组件绑定一个随当前
QuestionIndex、currentQuestion唯一变化的key属性,触发节点的重新创建:<app-radio-question [attr.key]="'radio-' + currentQuestion.id + '-' + QuestionIndex" [text]="currentQuestion.text" *ngIf="currentQuestion.type == 'RadioQuestion' && !showEndQuestion" [title]="currentQuestion.title" [NoOptionText]="currentQuestion.noOption" [YesOptionText]="currentQuestion.yesOption" [cacheData]="cacheData" (formChangedEvent)="OnQuestionResponse($event)"> </app-radio-question> - 原理:Angular的DOM diff算法会对比节点的key值,一旦key变化,就会判定为这是一个全新的节点,直接销毁旧的组件实例,重新执行组件的构造函数、
ngOnInit、表单初始化全流程,组件会达到和首次加载完全一致的状态。 - 注意:如果上述key绑定方式没有触发重建,可以用
<ng-container>包裹组件,在容器的*ngIf判断中加入QuestionIndex作为判断值,强制触发节点的重新比对。
常见误区规避
- 不要在
ngOnChanges的重置逻辑中修改任何被@Input装饰的传入属性,否则必然会再次触发ngOnChanges形成循环,所有重置操作只针对组件内部的自有状态即可 - 不要在子组件的变更检测周期(比如
ngAfterViewChecked、ngDoCheck)中直接修改绑定到模板的值,否则会触发ExpressionChangedAfterItHasBeenCheckedError;如果确实需要在这些时机修改值,要通过ChangeDetectorRef.detectChanges()手动标记变更,避免报错
内容的提问来源于stack exchange,提问作者Marc Rasmussen
相关产品推荐
相关产品推荐

