3

Angular响应式表单在reset之后表单状态还是invalid的问题探索

 2 years ago
source link: https://www.huhexian.com/22045.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Angular响应式表单在reset之后表单状态还是invalid的问题探索

青山 2022-01-0813:36:27评论4979字

做表单提交的时候,很多的场景是提交完一个表单后跳转页面,或者页面刷新,或者表单重绘,这种场景下,等于表单是一次性的,提交后不用管当前表单的状态。

Angular响应式表单在reset之后表单状态还是invalid的问题探索

但是假设另一种场景,我们填写表单后提交,表单不重新绘制,我们需要手动将表单回复到初始状态。这个情况下我们使用响应式表单的reset()方法即可。但是遇到个问题,当我调用formGroup.reset()的时候,发现页面上的表单的验证状态没有被重置,也就是说还是调用reset之后,表单的状态会变为invalid。这很蛋疼。

场景
我们的应用场景是一个表单,html里面有一个表单:

  1. <form [formGroup]="form" class="create-form">
  2. <mat-form-field floatLabel="never" class="m-r-10">
  3. <input matInput autocomplete="off" placeholder="计划内容" formControlName="content">
  4. </mat-form-field>
  5. <mat-form-field floatLabel="never">
  6. <input matInput [matDatepicker]="picker" [min]="minDate" placeholder="截止时间" formControlName="endTime">
  7. <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  8. <mat-datepicker #picker disabled="false"></mat-datepicker>
  9. </mat-form-field>
  10. <button mat-button color="primary" tpe="submit" class="m-r-10">添加</button>
  11. </form>

ts文件中对应的提交方法:

  1. export class TodoComponent implements OnInit {
  2. @Output() submitEvent: EventEmitter<TodoInterface> = new EventEmitter<TodoInterface>();
  3. form: FormGroup;
  4. minDate: Date;
  5. constructor(
  6. private fb: FormBuilder,
  7. ngOnInit(): void {
  8. this.minDate = new Date();
  9. this.form = this.fb.group({
  10. content: [null, Validators.required],
  11. endTime: [moment().add(1, 'days'), Validators.required],
  12. isCompleted: false,
  13. submit() {
  14. this.form.updateValueAndValidity();
  15. if (this.form.invalid) {
  16. return;
  17. const endTime = this.form.getRawValue().endTime;
  18. const params = this.form.getRawValue() as TodoInterface;
  19. params.endTime = (endTime as Moment).valueOf();
  20. this.submitEvent.emit(params);
  21. this.form.reset({
  22. content: null,
  23. endTime: moment().add(1, 'days'),
  24. isCompleted: false,

然后现在出现的问题是,当点击提交后,“计划内容”的输入框是红色的invalid状态。

探索路程
首先google搜索,找到一个质量比较高的angular components issuess,发现问题是仅仅重置FormGroup还不够,需要重置实际表单的提交状态。那么如何重置表单?

投机的方法
看到一个比较简单(投机)的方法,直接在submit方法中传递$event,然后用currentTarget来reset表单。尝试了下,可以直接解决问题。看看下。

先看html:

  1. <form [formGroup]="form" class="create-form" (ngSubmit)="submit($event)">
  2. <form>

ts文件中的改动:

  1. submit(event) {
  2. this.form.updateValueAndValidity();
  3. if (this.form.invalid) {
  4. return;
  5. const endTime = this.form.getRawValue().endTime;
  6. const params = this.form.getRawValue() as TodoInterface;
  7. params.endTime = (endTime as Moment).valueOf();
  8. this.submitEvent.emit(params);
  9. console.log(event.currentTarget);
  10. (event.currentTarget as HTMLFormElement).reset();

这里event.currentTarget是当前处理事件的目标dom节点,对应到ts中的类型的话就是HTMLFormElement,它是原生的dom,那么里面的reset方法只是重置掉这个Form。

这个看起来是能满足我们的需要,但是假设我们要在重置的时候传递值,那么是行不通的,这个方法不接受传值。

验证器的触发错误状态的条件是isInvalid && (isTouched || isSumbitted)。所以当点击提交的时候,当前表单的isSumbitted是ture,我们需要使用ViewChild的方式将表单的提交状态重置掉。

使用FormGroupDirective的resetForm来重置表单
在ts中进行修改:

  1. export class TodoComponent implements OnInit {
  2. @ViewChild(FormGroupDirective) myForm;
  3. // other code ...
  4. submit() {
  5. this.form.updateValueAndValidity();
  6. if (this.form.invalid) {
  7. return;
  8. const endTime = this.form.getRawValue().endTime;
  9. const params = this.form.getRawValue() as TodoInterface;
  10. params.endTime = (endTime as Moment).valueOf();
  11. this.submitEvent.emit(params);
  12. if (this.myForm) {
  13. this.myForm.resetForm();

发现还是老样子,很蛋疼。

但又一想,我是把提交事件放在了提交按钮上,但是对于这个表单,它并不认识这个按钮。。。

那我们的html是不是有什么问题?

相应的改一下html:

  1. <form [formGroup]="form" class="create-form" (ngSubmit)="submit()">
  2. <mat-form-field floatLabel="never" class="m-r-10">
  3. <input matInput autocomplete="off" placeholder="计划内容" formControlName="content">
  4. </mat-form-field>
  5. <mat-form-field floatLabel="never">
  6. <input matInput [matDatepicker]="picker" [min]="minDate" placeholder="截止时间" formControlName="endTime">
  7. <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
  8. <mat-datepicker #picker disabled="false"></mat-datepicker>
  9. </mat-form-field>
  10. <button mat-button color="primary" type="submit" class="m-r-10">添加</button>
  11. </form>

然后再测试一下,竟然OK了,

还是得规范点。。。

那我们来看下@ViewChild(FormGroupDirective) myForm这个声明起啥用。

我们知道ViewChild是一个视图查询的属性装饰器,它会去查找视图中的第一个FormGroup指令,然后我们可以在ts中消费它。

那对应的,FormGroupDirective.resetForm()方法是何方神圣?

FormGroupDirective是继承于ControlContainer的,类的定义为:

  1. 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:

  1. @Directive({
  2. selector: '[formGroup]',
  3. providers: [formDirectiveProvider],
  4. host: {'(submit)': 'onSubmit($event)', '(reset)': 'onReset()'},
  5. exportAs: 'ngForm'

那么我们可以使用模板语法获得这个表单:

  1. <form [formGroup]="form" class="create-form" (ngSubmit)="submit()" #myForm="ngForm">
  2. </form>

我们声明了一个myForm的模板变量以获得FormGroupDirective。然后在ts中需要用@ViewChild来绑定:

  1. export class TodoComponent implements OnInit {
  2. @ViewChild('myForm') myForm: FormGroupDirective;
  3. // other code ...
  4. submit() {
  5. this.form.updateValueAndValidity();
  6. if (this.form.invalid) {
  7. return;
  8. const endTime = this.form.getRawValue().endTime;
  9. const params = this.form.getRawValue() as TodoInterface;
  10. params.endTime = (endTime as Moment).valueOf();
  11. this.submitEvent.emit(params);
  12. if (this.myForm) {
  13. this.myForm.resetForm({
  14. content: null,
  15. endTime: moment().add(1, 'days'),
  16. isCompleted: false,

其实问题不是个大问题,就是比较细节,而且我们粗暴点完全可以不理会,直接使用ngIf来消灭大多数问题。但是简单粗暴代表着不想深究,解决得了一时,以后确实有这个需求的时候不是抓瞎了么。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK