CKEditor值传递至父组件触发ExpressionChangedAfterItHasBeenCheckedError问题
解决Angular中CKEditor组件的ExpressionChangedAfterItHasBeenCheckedError错误
这个错误本质是Angular的变更检测机制在同一检测周期内检测到了值的变化——简单来说就是组件已经完成了一次变更检查,但之后又有代码修改了绑定的值,导致Angular认为状态不一致。结合你的代码来看,问题出在子组件和父组件的双向同步循环以及错误的emit时机上。
问题根源分析
- 子组件的
ngOnChanges会在@Input() text变化时触发,而你在这个钩子里面直接emit了form控件值; - 父组件接收到
textValue事件后,修改了inputData.title,而这个值正是子组件的@Input() text; - 这就形成了一个闭环:子组件emit → 父组件修改Input值 → 子组件ngOnChanges触发 → 再次emit,而且这一系列操作都发生在同一个变更检测周期内,直接触发了Angular的错误。
另外,你在ngAfterViewChecked里尝试的emit也不对,这个钩子会在每次视图检查后触发,同样会导致不必要的循环和变更检测冲突。
修正方案
我们需要调整emit的时机,只在编辑器实际被用户修改时触发事件,同时正确同步Input值到form控件,避免循环触发。
1. 修改子组件(TextEditorWithLimitedWidgetsComponent)
export class TextEditorWithLimitedWidgetsComponent implements OnInit, OnChanges { constructor( private fb: FormBuilder, private fileValidations: FileValidations, private cdref: ChangeDetectorRef ) { } @Input() text: string; @Output() textValue = new EventEmitter<string>(); // 明确emit的是字符串类型,更清晰 form: FormGroup; ngOnInit() { this.form = this.fb.group({ text: ['', [ Validators.required, CustomValidator.textEditor(30) ]] }); // 初始化时同步Input值到form this.syncFormWithInputText(); } get f() { return this.form.controls; } // 专门的方法同步Input值到form控件 private syncFormWithInputText() { if (this.form && this.text !== undefined) { this.form.patchValue({ text: this.text }, { emitEvent: false }); // 禁用patchValue的事件,避免触发不必要的onValueChange } } ngOnChanges(changes: SimpleChanges): void { // 当Input的text变化时,只同步到form,不要emit事件 if (changes['text'] && !changes['text'].firstChange) { this.syncFormWithInputText(); } } // 只在编辑器实际变化时emit值 onValueChange(e) { // 获取编辑器的最新内容 const editorContent = e.editor.getData(); this.textValue.emit(editorContent); // 如果需要手动触发变更检测,这里调用即可,不过通常不需要,如果还是有问题再加上 // this.cdref.detectChanges(); } }
对应的子组件模板,其实不需要同时绑定[data]和formControlName,因为formControlName已经会同步值,所以可以去掉[data]:
<ckeditor tagName="textarea" [config]="config" (change)="onValueChange($event)" formControlName="text"></ckeditor>
2. 修改父组件逻辑
父组件只需要接收子组件emit的编辑器内容,处理后更新inputData.title即可,这里如果还是遇到变更检测问题,可以手动标记需要检测:
constructor(private fileValidations: FileValidations, private cdref: ChangeDetectorRef) {} getTextValue(editorContent: string) { const dataWithHTMLTags = editorContent.toString(); this.inputData.title = this.fileValidations.stringsWithoutHTMLTags(dataWithHTMLTags); // 如果还是有错误,手动标记变更检测 this.cdref.markForCheck(); console.log(this.inputData.title); }
父组件模板里的(input)绑定可以去掉,因为我们已经通过子组件的textValue事件来同步值了:
<app-editor [descriptionLimit]="50" [text]="inputData.title" (textValue)="getTextValue($event)"></app-editor>
额外注意点
- 避免在
ngOnChanges、ngAfterViewChecked这类变更检测钩子中修改绑定值或者emit事件,这些钩子是Angular用来确认状态的,在里面修改值很容易触发循环或检测错误; - 使用
patchValue时加上{ emitEvent: false }可以避免同步Input值时触发编辑器的change事件,防止不必要的循环; - 如果你的CKEditor版本是较新的,也可以考虑使用
(ngModelChange)来替代(change)事件,不过核心逻辑都是一样的:只在用户修改编辑器内容时通知父组件。
内容的提问来源于stack exchange,提问作者Ayushi Verma
相关产品推荐
相关产品推荐

