视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Angular中响应式表单的三种更新值方法详析
2020-11-27 22:32:00 责编:小采
文档


前言

众所周知Angular响应式表单相比较模板驱动表单更大操作性、更易测试性。因此,我更推荐这类表单创造方式。

当一个用于修改用户信息的表单,数据的来源总是来自远程;而对于一个 FormGroup 的创建总在 ngOnInit 中完成。因此,这里会有一个表单更新值的问题。

而通常我们会透过 FormGroup 下的三种方式 setValue、patchValue、reset 将值写入表单当中。

当然,或许我说的这三种方式时你压根就没有做过,那说明在表单上你依赖的是双向绑定 [(ngModel)],这本身就不是符合 Angular 响应式表单的牛B之处了。因此,在此我们不讨论这种这种方式。下面来一起看看详细的介绍:

一、创建响应式表单

我们模拟一个用户信息修改的表单所需要的字段,可能包括:email、nickname 等。

如果以API的方式与现实字段之间产生一个关联,那么 FormGroup 表示一个表单,FormControl 表示表单中的字段。因此,FormControl 必须包裹在 FromGroup 下面。

下面,我们先简单的构建一个响应式表单。

别忘记导入 ReactiveFormsModule 模块。

@Component({
 selector: 'app-validation',
 template: `
 <form [formGroup]="form" (ngSubmit)="_submitForm(form)">
 <input type="email" formControlName="email">
 <input type="text" formControlName="nickname">
 <button type="submit" [disabled]="form.invalid">Submit</button>
 </form>
 `
})
export class UserEditComponent {
 constructor(private fb: FormBuilder, private route: ActivatedRoute) {}
 
 ngOnInit() {
 this.form = this.fb.group({
 email: ['', Validators.compose([Validators.required, Validators.email])],
 nickname: ['', [Validators.required]]
 });
 
 this.route.params
 .switchMap((params: Params) => loadUser(+params['id']))
 .subscribe(data => {
 // Updating value
 });
 }
 
 loadUser() {
 return Observable.of({ email: 'xx@xx.com', nickname: 'cipchk' }).delay(1000);
 }
 
 _submitForm({ value }) {
 // Save value
 }
}

以上的这些代码再熟悉不过了。假设 UserEditComponent 是由路由 /user/edit/1 触发,那么会发生几个几件事情。

首先,创建一个空的响应式表单 form。

this.form = this.fb.group({
 email: ['', Validators.compose([Validators.required, Validators.email])],
 nickname: ['', [Validators.required]]
});

表单的内容有 email、nickname 两个字段。

  • email 必填项且必须是标准 Email 格式。
  • nickname 必填项。
  • 然而,HTML中,除了 formGroup、formControlName 的配置以外,也看不到任何有关对表单的校验代码。但,当我们输入一个无效 Email 时 input 会自动加上 ng-invalid 类。

    这便是响应式表单的魅力。

    现在我们回到正题,将分别针对 setValue、patchValue、reset 三种不同更新表单值实际上会发生什么。

    二、patchValue

    正如名称那般,打补丁。假如我们在 email 文本框里输入:xx@xx.com,接着调用:

    this.form.patchValue({ nickname: 'cipchk' });

    最终的结果是两个字段同时拥有值,因为这里我们只对 nickname 设置了值,而 email 并没有,那只是先前人为录入的数据。

    那么 patchValue 实际上做了什么呢?

    patchValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
     Object.keys(value).forEach(name => {
     if (this.controls[name]) {
     this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
     }
     });
     this.updateValueAndValidity(options);
    }

    首先,利用 Object.keys 查找主键,并以主键名查找相应的 FromControl 实例对象:

    Object.keys({ nickname: 'cipchk' }).forEach(name => { 
     console.log(name); 
    });
    // [ 'nickname' ]

    然后,更新值:

    this.controls[name].patchValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});

    而 FromControl 实例的 patchValue 和 FromGroup 不同,他只是单纯的更新 FromControle 实例对象中的 value 值。

    value 相当于表单实际值,还记得先前HTML中的 formControlName 就是将实例与DOM产生联系,这也就是为什么不需要在DOM中使用双向绑定的原因。

    三、setValue

    跟 patchValue 有一点不一样,当我们提供一个 FromGroup 中并不存在的字段时,会抛出一个错误。除此之外,与 patchValue 并无不同。

    setValue(value: {[key: string]: any}, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
     this._checkAllValuesPresent(value);
     Object.keys(value).forEach(name => {
     this._throwIfControlMissing(name);
     this.controls[name].setValue(value[name], {onlySelf: true, emitEvent: options.emitEvent});
     });
     this.updateValueAndValidity(options);
    }

    主要是 this._throwIfControlMissing(name); 当传递的对象有一个不是 FromControl 时直接抛弃一个 Error。

    _throwIfControlMissing(name: string): void {
     if (!Object.keys(this.controls).length) {
     throw new Error(`
     There are no form controls registered with this group yet. If you're using ngModel,
     you may want to check next tick (e.g. use setTimeout).
     `);
     }
     if (!this.controls[name]) {
     throw new Error(`Cannot find form control with name: ${name}.`);
     }
    }

    四、reset

    正常情况下,表单需要提供一个重置按钮时调用此方法。

    reset(formState: any = null, options: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
     this._applyFormState(formState);
     this.markAsPristine(options);
     this.markAsUntouched(options);
     this.setValue(this._value, options);
    }

    除了恢复校验状态以外。最后一句代码是调用 setValue,这等同上一节说的。因此,当我们调用此方法时,允许我们直接传递一个数据对象做为重置后的默认值,比如:

    <button (click)="form.reset({ nickname: 'xx' })">Reset</button>

    重置表单后并设置 nickname 默认值为:xx。

    结论

    每一种不同更新值方式都会有不一样的结果,当我们回头过看开头中留下来的:

    // Updating value

    如果是你,你会怎么写呢?

    总结

    下载本文
    显示全文
    专题