做表单提交的时候,很多的场景是提交完一个表单后跳转页面,或者页面刷新,或者表单重绘,这种场景下,等于表单是一次性的,提交后不用管当前表单的状态。
但是假设另一种场景,我们填写表单后提交,表单不重新绘制,我们需要手动将表单回复到初始状态。这个情况下我们使用响应式表单的reset()
方法即可。但是遇到个问题,当我调用formGroup.reset()
的时候,发现页面上的表单的验证状态没有被重置,也就是说还是调用reset
之后,表单的状态会变为invalid
。这很蛋疼。
场景
我们的应用场景是一个表单,html里面有一个表单:
<form [formGroup]="form" class="create-form"> <mat-form-field floatLabel="never" class="m-r-10"> <input matInput autocomplete="off" placeholder="计划内容" formControlName="content"> </mat-form-field> <mat-form-field floatLabel="never"> <input matInput [matDatepicker]="picker" [min]="minDate" placeholder="截止时间" formControlName="endTime"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker disabled="false"></mat-datepicker> </mat-form-field> <button mat-button color="primary" tpe="submit" class="m-r-10">添加</button> </form>
ts文件中对应的提交方法:
export class TodoComponent implements OnInit { @Output() submitEvent: EventEmitter<TodoInterface> = new EventEmitter<TodoInterface>(); form: FormGroup; minDate: Date; constructor( private fb: FormBuilder, ) { } ngOnInit(): void { this.minDate = new Date(); this.form = this.fb.group({ content: [null, Validators.required], endTime: [moment().add(1, 'days'), Validators.required], isCompleted: false, }); } submit() { this.form.updateValueAndValidity(); if (this.form.invalid) { return; } const endTime = this.form.getRawValue().endTime; const params = this.form.getRawValue() as TodoInterface; params.endTime = (endTime as Moment).valueOf(); this.submitEvent.emit(params); this.form.reset({ content: null, endTime: moment().add(1, 'days'), isCompleted: false, }); } }
然后现在出现的问题是,当点击提交后,“计划内容”的输入框是红色的invalid
状态。
探索路程
首先google搜索,找到一个质量比较高的angular components issuess,发现问题是仅仅重置FormGroup
还不够,需要重置实际表单的提交状态。那么如何重置表单?
投机的方法
看到一个比较简单(投机)的方法,直接在submit
方法中传递$event
,然后用currentTarget
来reset
表单。尝试了下,可以直接解决问题。看看下。
先看html:
<form [formGroup]="form" class="create-form" (ngSubmit)="submit($event)"> <form>
ts文件中的改动:
submit(event) { this.form.updateValueAndValidity(); if (this.form.invalid) { return; } const endTime = this.form.getRawValue().endTime; const params = this.form.getRawValue() as TodoInterface; params.endTime = (endTime as Moment).valueOf(); this.submitEvent.emit(params); console.log(event.currentTarget); (event.currentTarget as HTMLFormElement).reset(); }
这里event.currentTarget
是当前处理事件的目标dom节点,对应到ts中的类型的话就是HTMLFormElement
,它是原生的dom,那么里面的reset
方法只是重置掉这个Form。
这个看起来是能满足我们的需要,但是假设我们要在重置的时候传递值,那么是行不通的,这个方法不接受传值。
验证器的触发错误状态的条件是isInvalid && (isTouched || isSumbitted)
。所以当点击提交的时候,当前表单的isSumbitted
是ture
,我们需要使用ViewChild
的方式将表单的提交状态重置掉。
使用FormGroupDirective的resetForm来重置表单
在ts中进行修改:
export class TodoComponent implements OnInit { (FormGroupDirective) myForm; // other code ... submit() { this.form.updateValueAndValidity(); if (this.form.invalid) { return; } const endTime = this.form.getRawValue().endTime; const params = this.form.getRawValue() as TodoInterface; params.endTime = (endTime as Moment).valueOf(); this.submitEvent.emit(params); if (this.myForm) { this.myForm.resetForm(); } } }
发现还是老样子,很蛋疼。
但又一想,我是把提交事件放在了提交按钮上,但是对于这个表单,它并不认识这个按钮。。。
那我们的html是不是有什么问题?
相应的改一下html:
<form [formGroup]="form" class="create-form" (ngSubmit)="submit()"> <mat-form-field floatLabel="never" class="m-r-10"> <input matInput autocomplete="off" placeholder="计划内容" formControlName="content"> </mat-form-field> <mat-form-field floatLabel="never"> <input matInput [matDatepicker]="picker" [min]="minDate" placeholder="截止时间" formControlName="endTime"> <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle> <mat-datepicker #picker disabled="false"></mat-datepicker> </mat-form-field> <button mat-button color="primary" type="submit" class="m-r-10">添加</button> </form>
然后再测试一下,竟然OK了,😓
还是得规范点。。。
那我们来看下@ViewChild(FormGroupDirective) myForm
这个声明起啥用。
我们知道ViewChild
是一个视图查询的属性装饰器,它会去查找视图中的第一个FormGroup
指令,然后我们可以在ts中消费它。
那对应的,FormGroupDirective.resetForm()
方法是何方神圣?
FormGroupDirective
是继承于ControlContainer
的,类的定义为:
export declare class FormGroupDirective extends ControlContainer implements Form, OnChanges {}
这个指令就是为了将FormGroup
绑定到DOM元素,所以我们在ts中使用视图查询得到的myForm
,就是完整的FormGroup
指令,那么它提供的resetForm
方法具有重置表单的值和“重置表单的提交状态”这两个功能。
相对应的,FormGroup
的reset
方法就只有重置表单的值的功能了。
但这种方式适合于该组件只有一个FormGroup
表单的,那假设有多个的时候这个方法也就不适用了。
使用模板变量精确控制
使用模板变量之前,我们需要知道FormGroupDirective
有没有exportAs
这个属性。exportAs
定义了一个名字,用于在模板中将该指令赋值给一个变量。查看Angular源码中的form_group_directive.ts 我们可以发现,它被定义为了ngForm
:
@Directive({ selector: '[formGroup]', providers: [formDirectiveProvider], host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'}, exportAs: 'ngForm' })
那么我们可以使用模板语法获得这个表单:
<form [formGroup]="form" class="create-form" (ngSubmit)="submit()" #myForm="ngForm"> ... </form>
我们声明了一个myForm
的模板变量以获得FormGroupDirective
。然后在ts中需要用@ViewChild
来绑定:
export class TodoComponent implements OnInit { @ViewChild('myForm') myForm: FormGroupDirective; // other code ... submit() { this.form.updateValueAndValidity(); if (this.form.invalid) { return; } const endTime = this.form.getRawValue().endTime; const params = this.form.getRawValue() as TodoInterface; params.endTime = (endTime as Moment).valueOf(); this.submitEvent.emit(params); if (this.myForm) { this.myForm.resetForm({ content: null, endTime: moment().add(1, 'days'), isCompleted: false, }) } } }
非常好,完美!
其实问题不是个大问题,就是比较细节,而且我们粗暴点完全可以不理会,直接使用ngIf
来消灭大多数问题。但是简单粗暴代表着不想深究,解决得了一时,以后确实有这个需求的时候不是抓瞎了么。
找到一种比较好的解决方案是比较爽的,而且以后可以复用这个逻辑。o( ̄▽ ̄)d
最新评论