提交后Angular响应式表单某FormControl失效及ngOnInit重复触发问题
Angular表单提交后订阅回调重复触发及字段失效问题分析
问题场景
我通过ngOnInit初始化Angular响应式表单,从Store中获取值并调用patchValue更新表单。提交表单时,会分发一个绑定到Effect的action,将设置保存至localStorage。但分发该action后,store.select(selectSettings)的订阅回调会再次执行(看起来像ngOnInit重复触发),且sqs_queue下的url控件变为无效;移除action分发则不会出现此问题。
相关代码
ngOnInit方法
ngOnInit(): void { this.initForm() this.store.select(selectSettings).subscribe({ next: (settings) => { console.log('Getting settings from store', settings) this.isLoading = false this.settingsForm.patchValue(settings) }, error: () => { // ... } }) }
表单初始化方法initForm
initForm() { const URL_REGEXP = /(^|\s)((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi this.settingsForm = this.fb.group({ // other fields bgjob: this.fb.group({ sqs_queue: this.fb.group({ url: new FormControl('', [Validators.required, Validators.pattern(URL_REGEXP)]), profile: new FormControl('') }) }) }) }
提交方法onSubmit
onSubmit() { this.settingsService .saveSettings(this.settingsForm.value) .subscribe({ next: () => { // ... this.store.dispatch(set({settings: this.settingsForm.value})); }, error: (err) => { // ... } }); }
表单模板
<form [formGroup]="settingsForm" (ngSubmit)="settingsForm.valid && onSubmit()"> <!-- other stuff --> <mat-form-field formGroupName="sqs_queue" floatLabel="auto"> <mat-label>SQS Queue URL</mat-label> <input matInput placeholder="" formControlName="url" > </mat-form-field> <!-- other stuff --> <button mat-flat-button color="primary" type="submit" [disabled]="!settingsForm.valid || isLoading" > Submit </button> </form>
对应的Effect
saveSettings = createEffect(() => this.actions$.pipe( ofType(set), tap((action) => { localStorage.setItem('settings', JSON.stringify(action.settings)); }) ), { dispatch: false } );
疑问解答与分析
1. 问题出现的原因
- 订阅回调重复执行:并非
ngOnInit被重新调用,而是你在ngOnInit中订阅了store.select(selectSettings),分发setaction后Store的settings状态更新,触发了订阅的next回调,让你误以为ngOnInit重复触发。 - url字段变为无效:
patchValue会重新触发字段校验。你提交的this.settingsForm.value是合法的,但存入localStorage再从Store取出时,JSON序列化/反序列化可能导致url值的格式细节丢失,或者Store的状态更新逻辑间接修改了值,导致不符合正则校验规则,最终校验失败。
2. 分发action时避免问题的更优方案
你尝试的提交前取消订阅方案能暂时解决问题,但会导致组件后续无法响应Store的状态更新,不推荐。以下是更稳妥的方案:
方案一:用takeUntil管理订阅+提交状态标记
通过takeUntil确保组件销毁时自动取消订阅,同时添加提交状态标记,跳过提交过程中的表单更新:
import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; private destroy$ = new Subject<void>(); private isSubmitting = false; ngOnInit(): void { this.initForm() this.store.select(selectSettings) .pipe(takeUntil(this.destroy$)) .subscribe({ next: (settings) => { console.log('Getting settings from store', settings) this.isLoading = false; // 提交过程中跳过表单更新,避免覆盖用户输入或触发无效校验 if (!this.isSubmitting) { this.settingsForm.patchValue(settings); } }, error: () => { // ... } }); } onSubmit() { this.isSubmitting = true; this.settingsService .saveSettings(this.settingsForm.value) .subscribe({ next: () => { // ... this.store.dispatch(set({settings: this.settingsForm.value})); this.isSubmitting = false; }, error: (err) => { this.isSubmitting = false; // ... } }); } ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); }
方案二:用distinctUntilChanged过滤重复状态
如果Store的settings状态在提交前后没有实际变化,可通过distinctUntilChanged过滤重复值,避免回调重复执行:
import { distinctUntilChanged } from 'rxjs/operators'; ngOnInit(): void { this.initForm() this.store.select(selectSettings) .pipe( takeUntil(this.destroy$), // 自定义深度比较逻辑,替代JSON.stringify(性能更优) distinctUntilChanged((prev, curr) => { return prev.bgjob?.sqs_queue?.url === curr.bgjob?.sqs_queue?.url; }) ) .subscribe({ next: (settings) => { console.log('Getting settings from store', settings) this.isLoading = false; this.settingsForm.patchValue(settings); }, error: () => { // ... } }); }
方案三:修正patchValue的触发逻辑
调用patchValue时设置emitEvent: false,避免触发值变更事件导致重复校验:
this.settingsForm.patchValue(settings, { emitEvent: false });
注意:此方法仅能避免事件触发,若Store中的settings本身不符合校验规则,仍会导致字段无效,需确保存入Store的是表单校验后的合法值。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

