zoukankan      html  css  js  c++  java
  • [转]Angular Reactive Forms -- Model-Driven Forms响应式表单

     

    转自:https://segmentfault.com/a/1190000009041192
     
    Angular 4.x 中有两种表单:
    • Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )  官方文档:https://v2.angular.cn/docs/ts/latest/guide/forms.html

    • Reactive Forms (Model-Driven Forms) - 响应式表单  官方文档: https://v2.angular.cn/docs/ts/latest/guide/reactive-forms.html

    Template-Driven Forms vs Reactive Forms

    Template-Driven Forms (模板驱动表单) 的特点

    • 使用方便

    • 适用于简单的场景

    • 通过 [(ngModel)] 实现数据双向绑定

    • 最小化组件类的代码

    • 不易于单元测试

    • 异步的,模板驱动表单会委托指令来创建它们的表单控件。 为了消除“检查完后又变化了”的错误,这些指令需要消耗一个以上的变更检测周期来构建整个控件树。 这意味着在从组件类中操纵任何控件之前,我们都必须先等待一个节拍。

    Reactive Forms (响应式表单) 的特点

    • 比较灵活

    • 适用于复杂的场景

    • 简化了HTML模板的代码,把验证逻辑抽离到组件类中

    • 方便的跟踪表单控件值的变化

    • 易于单元测试

    • 同步的 ,使用响应式表单,我们会在代码中创建整个表单控件树。 我们可以立即更新一个值或者深入到表单中的任意节点,因为所有的控件都始终是可用的。

    使用 reactive forms 表单前,我们必须在 @NgModule 中导入 @angular/forms 库中的 ReactiveFormsModule

    复制代码
    import { ReactiveFormsModule } from '@angular/forms';
    
    @NgModule({
      imports: [
        ...,
        ReactiveFormsModule
      ],
      declarations: [...],
      bootstrap: [...]
    })
    export class AppModule {}
    复制代码

    友情提示:若使用 reactive forms,则导入 ReactiveFormsModule;若使用 template-driven 表单,则导入 FormsModule。

    FormControl 和 FormGroup

    FormControl - 它是一个为单个表单控件提供支持的类,可用于跟踪控件的值和验证状态,此外还提供了一系列公共API。

    使用示例:

    ngOnInit() {
      this.myControl = new FormControl('Semlinker');
    }

    FormGroup - 包含是一组 FormControl 实例,可用于跟踪 FormControl 组的值和验证状态,此外也提供了一系列公共API。

    使用示例:

    复制代码
    ngOnInit() {
      this.myGroup = new FormGroup({
        name: new FormControl('Semlinker'),
        location: new FormControl('China, CN')
      });
    }
    复制代码

    现在我们已经创建了 FormControl 和 FormGroup 实例,接下来我们来看一下如何使用:

    <form novalidate [formGroup]="myGroup">
      Name: <input type="text" formControlName="name">
      Location: <input type="text" formControlName="location">
    </form>

    注意事项:Template-Driven Forms 中介绍的 ngModel 和 name="" 属性,已经被移除了。这是一件好事,让我们的模板更简洁。

    上面示例中,我们必须使用 [formGroup] 绑定我们创建的 myGroup 对象,除此之外还要使用 formControlName 指令,绑定我们创建的 FormControl 控件。此时的表单结构如下:

    FormGroup -> 'myGroup'
        FormControl -> 'name'
        FormControl -> 'location'

    User interface

    signup.interface.ts

    复制代码
    export interface User {
      name: string;
      account: {
        email: string;
        confirm: string;
      }
    }
    复制代码

    与之对应的表单结构如下:

    FormGroup -> 'user'
        FormControl -> 'name'
        FormGroup -> 'account'
            FormControl -> 'email'
            FormControl -> 'confirm'

    是的,我们可以创建嵌套的 FormGroup 集合!让我们更新一下组件 (不包含初始数据):

    复制代码
    import { Component, OnInit } from '@angular/core';
    import { FormControl, FormGroup } from '@angular/forms';
    
    @Component({...})
    export class SignupFormComponent implements OnInit {
      user: FormGroup;
      ngOnInit() {
        this.user = new FormGroup({
          name: new FormControl(''),
          account: new FormGroup({
            email: new FormControl(''),
            confirm: new FormControl('')
          })
        });
      }
    }
    复制代码

    如果我们想要设置初始数据,我们可以按照上述示例进行设置。通常情况下,我们通过服务端提供的 API 接口来获取表单的初始信息。

    绑定FormGroup模型

    复制代码
    <form novalidate [formGroup]="user">
      <label>
        <span>Full name</span>
        <input
          type="text"
          placeholder="Your full name"
          formControlName="name">
      </label>
      <div formGroupName="account">
        <label>
          <span>Email address</span>
          <input
            type="email"
            placeholder="Your email address"
            formControlName="email">
        </label>
        <label>
          <span>Confirm address</span>
          <input
            type="email"
            placeholder="Confirm your email address"
            formControlName="confirm">
        </label>
      </div>
      <button type="submit">Sign up</button>
    </form>
    复制代码

    现在 FormGroup 与 FormControl 对象与 DOM 结构的关联信息如下:

    复制代码
    // JavaScript APIs
    FormGroup -> 'user'
        FormControl -> 'name'
        FormGroup -> 'account'
            FormControl -> 'email'
            FormControl -> 'confirm'
    
    // DOM bindings
    formGroup -> 'user'
        formControlName -> 'name'
        formGroupName -> 'account'
            formControlName -> 'email'
            formControlName -> 'confirm'
    复制代码

    当使用模板驱动的表单时,为了获取 f.value 表单的值,我们需要先执行 #f="ngForm" 的操作。而对于使用响应式的表单,我们可以通过以下方式,方便的获取表单的值:

    {{ user.value | json }} // { name: '', account: { email: '', confirm: '' }}

    Reactive submit

    跟模板驱动的表单一样,我们可以通过 ngSubmit 输出属性,处理表单的提交逻辑:

    <form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
      ...
    </form>

    需要注意的是,我们使用 user 对象作为 onSubmit() 方法的参数,这使得我们可以获取表单对象的相关信息,具体处理逻辑如下:

    复制代码
    export class SignupFormComponent {
      user: FormGroup;
      onSubmit({ value, valid }: { value: User, valid: boolean }) {
        console.log(value, valid);
      }
    }
    复制代码

    上面代码中,我们使用 Object destructuring (对象解构) 的方式,从user 对象中获取 value 和 valid 属性的值。其中 value的值,就是 user.value 的值。在实际应用中,我们是不需要传递 user 参数的:

    复制代码
    export class SignupFormComponent {
      user: FormGroup;
      onSubmit() {
        console.log(this.user.value, this.user.valid);
      }
    }
    复制代码

    表单的数据绑定方式和提交逻辑已经介绍完了,是该介绍表单实际应用中,一个重要的环节 — 表单验证。

    Reactive error validation

    接下来我们来为表单添加验证规则,首先我们需要从 @angular/forms 中导入 Validators。具体使用示例如下:

    复制代码
    ngOnInit() {
      this.user = new FormGroup({
        name: new FormControl('', [Validators.required, Validators.minLength(2)]),
        account: new FormGroup({
          email: new FormControl('', Validators.required),
          confirm: new FormControl('', Validators.required)
        })
      });
    }
    复制代码

    通过以上示例,我们可以看出,如果表单控制包含多种验证规则,可以使用数组声明多种验证规则。若只包含一种验证规则,直接声明就好。通过这种方式,我们就不需要在模板的输入控件中添加 required 属性。接下来我们来添加表单验证失败时,不允许进行表单提交功能:

    <form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
      ...
      <button type="submit" [disabled]="user.invalid">Sign up</button>
    </form>

    那么问题来了,我们要如何获取表单控件的验证信息?我们可以使用模板驱动表单中介绍的方式,具体如下:

    <form novalidate [formGroup]="user">
      {{ user.controls.name?.errors | json }}
    </form>

    友情提示:?.prop 称为安全导航操作符,用于告诉 Angular prop 的值可能不存在。

    此外我们也可以使用 FormGroup 对象提供的 API,来获取表单控件验证的错误信息:

    <form novalidate [formGroup]="user">
      {{ user.get('name').errors | json }}
    </form>

    完整代码:

    复制代码
    import { Component, OnInit } from '@angular/core';
    import { FormControl, FormGroup, Validators } from '@angular/forms';
    import { User } from './signup.interface';
    @Component({
      selector: 'signup-form',
      template: `
        <form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
          <label>
            <span>Full name</span>
            <input type="text" placeholder="Your full name" formControlName="name">
          </label>
          <div class="error" *ngIf="user.get('name').hasError('required') && 
                user.get('name').touched">
            Name is required
          </div>
          <div class="error" *ngIf="user.get('name').hasError('minlength') && 
                user.get('name').touched">
            Minimum of 2 characters
          </div>
          <div formGroupName="account">
            <label>
              <span>Email address</span>
              <input type="email" placeholder="Your email address" formControlName="email">
            </label>
            <div
              class="error"
              *ngIf="user.get('account').get('email').hasError('required') && 
                 user.get('account').get('email').touched">
                    Email is required
            </div>
            <label>
              <span>Confirm address</span>
              <input type="email" placeholder="Confirm your email address" 
                 formControlName="confirm">
            </label>
            <div
              class="error"
              *ngIf="user.get('account').get('confirm').hasError('required') && 
                 user.get('account').get('confirm').touched">
              Confirming email is required
            </div>
          </div>
          <button type="submit" [disabled]="user.invalid">Sign up</button>
        </form>
      `
    })
    export class SignupFormComponent implements OnInit {
      user: FormGroup;
      constructor() {}
      ngOnInit() {
        this.user = new FormGroup({
          name: new FormControl('', [Validators.required, Validators.minLength(2)]),
          account: new FormGroup({
            email: new FormControl('', Validators.required),
            confirm: new FormControl('', Validators.required)
          })
        });
      }
      onSubmit({ value, valid }: { value: User, valid: boolean }) {
        console.log(value, valid);
      }
    }
    复制代码

    功能是实现了,但创建 FormGroup 对象的方式有点繁琐,Angular 团队也意识到这点,因此为我们提供 FormBuilder ,来简化上面的操作。

    Simplifying with FormBuilder

    首先我们需要从 @angular/forms 中导入 FormBuilder

    复制代码
    import { FormBuilder, FormGroup, Validators } from '@angular/forms';
    
    export class SignupFormComponent implements OnInit {
      user: FormGroup;
      constructor(private fb: FormBuilder) {}
      ...
    }
    复制代码

    使用FormBuilder:

    复制代码
    @Component({...})
    export class SignupFormComponent implements OnInit {
      user: FormGroup;
      constructor(private fb: FormBuilder) {}
      ngOnInit() {
        this.user = this.fb.group({
          name: ['', [Validators.required, Validators.minLength(2)]],
          account: this.fb.group({
            email: ['', Validators.required],
            confirm: ['', Validators.required]
          })
        });
      }
      onSubmit({ value, valid }: { value: User, valid: boolean }) {
        console.log(value, valid);
      }
    }

  • 相关阅读:
    [JLOI2011] 飞行路线
    高精度运算模板
    Dijkstra算法模板
    [SDOI2010] 外星千足虫
    [SDOI2006] 线性方程组
    [CTSC2014] 企鹅QQ
    模板三连击:树状数组+线段树+主席树
    [ZJOI2008] 树的统计
    [国家集训队] 礼物
    [洛谷P4720] [模板] 扩展卢卡斯
  • 原文地址:https://www.cnblogs.com/sameen/p/13691227.html
Copyright © 2011-2022 走看看