• Template-Driven Forms 模板驱动式表单


    Angular 4.x 中有两种表单:

    • Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )

    • Reactive Forms - 响应式表单

    本文主要介绍 Template-Driven Forms (模板驱动式表单) ,将涉及 ngFormngModelngModelGroup、表单提交事件、表单验证和异常信息输出等内容。

    User interface

    // signup.interface.ts
    export interface User {
      name: string;
      account: {
        email: string;
        confirm: string;
      }
    }

    使用 template-driven 表单前,我们必须在 @NgModule 中导入 @angular/forms 库中的 FormModule

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

    Template-driven approach

    import { Component } from '@angular/core';
    import { User } from './signup.interface';
    
    @Component({
      selector: 'signup-form',
      template: `
        <form novalidate>...</form>
      `
    })
    export class SignupFormComponent {
      public user: User = {
        name: '',
        account: {
          email: '',
          confirm: ''
        }
      };
      constructor() {}
    }

    初始化 SignupFormComponent 组件类中的用户模型后,我们开始实现第一个功能点:即绑定 name、email、confirm 输入框的值

    Binding ngForm and ngModel

    我们从 ngForm 开始,更新后的模板如下:

    <form novalidate #f="ngForm">
      <label>
        <span>Full name</span>
        <input type="text" placeholder="Your full name">
      </label>
    </form>

    上面代码中,我们把 ngForm 的值赋值给 #f 变量,通过该变量我们可以方便的获取表单的值。

    友情提示:#f 变量的值,是 ngForm 指令的导出对象。novalidate属性是HTML5中的新属性,是一个布尔属性,规定当提交表单时不使用浏览器默认的规则对表单数据(输入)进行验证。

    在模板中,我们可以通过以下方式查看表单的值:

    {{ f.value | json }} // {}

    上面示例 f.value 输出 {},因为此时我们表单中还未绑定任何值。在 Angular 1.x 中我们可以使用 ng-model 指令进行表单数据的双向绑定,接下来我们来看一下 Angular 4.x 中怎么实现数据绑定。

    ngModel,[ngModel] and [(ngModel)]

    在 Angular 4.x 中 ngModel 有三种不同的语法:

    • ngModel - 直接使用 ngModel 指令,没有使用绑定或关联任何值。此时,ngModel 将自动关联表单控件的 name 属性,并使用该值作为 ngForm 对象的属性名。

    <form novalidate #f="ngForm">
      ...
        <input
         type="text"
         placeholder="Your full name"
         name="name"
         ngModel>
      ...
    </form>

    友情提示:上面示例中,如果 input 输入框若未设置 name 属性,应用将会抛出异常。ngModel 指令基于输入框的 name 属性,进行绑定。

    运行以上代码,f.value 的输入值如下:

    {{ f.value | json }} // { name: '' }

    非常好,我们已经绑定了 name 输入框的值。但我们应该怎么为输入框设置初始值?

    [ngModel] = one-way binding syntax (单向绑定语法)

    为了设置输入框初始值,我们先要更新一下 SignupFormComponent 组件类的用户模型:

    ...
    user: User = {
      name: 'Semlinker',
      account: {
        email: '',
        confirm: ''
      }
    };
    ...

    更新完用户模型,我们需要同步更新组件模板,具体如下:

    <form #f="ngForm">
      ...
        <input
          type="text"
          placeholder="Your full name"
          name="name"
          [ngModel]="user.name">
      ...
    </form>

    代码重新运行后,f.value 的输出如下:

    {{ f.value | json }} // { name: 'Semlinker' }

    从上面示例可以看出,使用 [ngModel] 允许我们通过 this.user.name 设置 name 输入框的初始值,而且该值会自动绑定到 f.value 对象上。

    友情提示:[ngModel] 是单向绑定,当表单中 name 输入框的值改变时,不会同步更新 this.user.name

    如果想在 name 输入框值变化时,自动同步更新 this.user.name 的值,我们需要使用双向绑定。

    • [(ngModel)] = two-way binding syntax (双向绑定),具体示例如下:

    <form #f="ngForm">
      ...
        <input
          type="text"
          placeholder="Your full name"
          name="name"
          [(ngModel)]="user.name">
      ...
    </form>

    上面示例成功运行后,我们可以在模板中新增以下代码,然后观察 user 模型的值:

    {{ user | json }} // { name: 'Semlinker' }

    需要注意的是,以下两种方式是等价的:

    <input [(ngModel)]="user.name">
    <input [ngModel]="user.name" (ngModelChange)="user.name = $event">

    其中 [(ngModel)] 是简写的语法糖。

    ngModels and ngModelGroup

    我们已经介绍了 ngForm 和 ngModel 的基础用法,现在我们来完善剩下的内容。SignupFormComponent 组件类的用户模型中,包含了一个嵌套属性 account ,account 对象中包含 email 和 confirm 属性,分为表示邮件地址和重复确认的邮件地址。针对这种场景,Angular 4.x 为我们提供了 ngModelGroup 指令,具体示例如下:

    <form novalidate #f="ngForm">
      <label>
        <span>Full name</span>
        <input
          type="text"
          placeholder="Your full name"
          name="name"
          ngModel>
      </label>
      <div ngModelGroup="account">
        <label>
          <span>Email address</span>
          <input
            type="email"
            placeholder="Your email address"
            name="email"
            ngModel>
        </label>
        <label>
          <span>Confirm address</span>
          <input
            type="email"
            placeholder="Confirm your email address"
            name="confirm"
            ngModel>
        </label>
      </div>
      <button type="submit">Sign up</button>
    </form>

    使用 ngModelGroup 指令后,我们的 DOM 结构将更加合理:

    ngForm -> '#f'
        ngModel -> 'name'
        ngModelGroup -> 'account'
                     -> ngModel -> 'email'
                     -> ngModel -> 'confirm'

    以上代码成功运行后,浏览器中页面显示的结果:

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

    此时我们已经完成了表单数据绑定,接下来我们来为表单增加提交逻辑。

    Template-driven submit

    Angular 表单中提供了 ngSubmit 输出属性,用于监听表单的提交事件:

    <form novalidate (ngSubmit)="onSubmit(f)" #f="ngForm">
      ...
    </form>

    当用户提交表单时,我们将会把 f 作为参数,调用 ngSubmit 关联的 onSubmit() 方法。onSubmit() 方法的具体实现如下:

    export class SignupFormComponent {
      user: User = {...};
      onSubmit({ value, valid }: { value: User, valid: boolean }) {
        console.log(value, valid);
      }
    }

    上面代码中,我们使用 Object destructuring (对象解构) 的方式,从#f 引用对象中获取 value 和 valid 属性的值。其中 value 的值,就是 f.value 的值。表单的数据绑定方式和提交逻辑已经介绍完了,是该介绍表单实际应用中,一个重要的环节 — 表单验证。

    Template-driven error validation

    在为表单项添加验证规则前,我们先来更新一下 SignupFormComponent 组件中的 Sign up 按钮,确保在表单验证不通过时,不允许用户执行表单提交操作。更新后的代码如下:

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

    以上代码我们通过 f.invalid 获取表单当前的验证状态 (验证不通过时该值为true),来控制按钮的 disabled 属性。

    接下来开始进入正题,为表单添加验证规则:

    <form novalidate #f="ngForm">
      <label>
        ...
        <input
          ...
          ngModel
          required>
      </label>
      <div ngModelGroup="account">
        <label>
          ...
          <input
            ...
            name="email"
            ngModel
            required>
        </label>
        <label>
          ...
          <input
            ...
            name="confirm"
            ngModel
            required>
        </label>
      </div>
      <button type="submit">Sign up</button>
    </form>

    #f 引用对象中有一个 controls 属性,通过该属性,我们就可以获取表单控件的验证信息,下面示例演示了如何获取 name 表单控件验证的异常信息:

    <form novalidate #f="ngForm">
      {{ f.controls.name?.errors | json }}
    </form>

    f.controls.name?.errors 的值是 null 或 undefined 时,表示验证成功。

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

    接下来为我们的 name 表单控件,添加显示异常信息的代码:

    <div *ngIf="f.controls.name?.required" class="error">
      Name is required
    </div>

    虽然我们已经可以获取某个表单项的验证信息,但有没有觉得使用 f.controls.name?.errors 这种方式,太麻烦了。那么有没有更简单的方式呢?我的答案是 - Yes !废话不多说,马上看示例:

    <label>
      ...
      <input
        ...
        #userName="ngModel"
        required>
    </label>
    <div *ngIf="userName.errors?.required" class="error">
      Name is required
    </div>

    (备注:此处一定要使用 #userName="ngModel")

    以上代码成功运行后,我们在浏览器中看到了异常信息,为了避免一开始就显示异常信息,我们可以更新一下 *ngIf 表达式的验证逻辑:

    <div *ngIf="userName.errors?.required && userName.touched" 
         class="error">
      Name is required
    </div>

    除了使用 required 验证规则之外,我们还可以使用 minlength (最小长度)、maxlength (最大长度) 等验证规则,下面我们继续来完善 SignupFormComponent 组件的功能,即为其它的表单控件添加显示异常信息的功能:

    <!-- name -->
    <div *ngIf="userName.errors?.required && userName.touched" 
         class="error">
      Name is required
    </div>
    <div *ngIf="userName.errors?.minlength && userName.touched" 
         class="error">
      Minimum of 2 characters
    </div>
    
    <!-- account: { email, confirm } -->
    <div *ngIf="userEmail.errors?.required && userEmail.touched" 
         class="error">
      Email is required
    </div>
    <div *ngIf="userConfirm.errors?.required && userConfirm.touched" 
         class="error">
      Confirming email is required
    </div>

    我们通过使用模板变量的方式,为 account 表单组添加了显示验证异常信息的功能。但有没有其它更好的方式呢?有没有办法去掉 userEmail 和 userConfirm 引用对象呢?答案是肯定的,具体示例如下:

    <div ngModelGroup="account" #userAccount="ngModelGroup">
      <label>
        <span>Email address</span>
        <input
          type="email"
          placeholder="Your email address"
          name="email"
          ngModel
          required>
      </label>
      <label>
        <span>Confirm address</span>
        <input
          type="email"
          placeholder="Confirm your email address"
          name="confirm"
          ngModel
          required>
      </label>
      <div *ngIf="userAccount.invalid && userAccount.touched" class="error">
        Both emails are required
      </div>
    </div>

    (备注:记得移除模板上的 #userEmail 和 #userConfirm 引用哈)

    表单控件的状态除了 touched 外,还包含其它几种状态?

    表单控件有以下 6 种状态,我们可以通过 #userName="ngModel" 方式获取对应的状态值。具体状态如下:

    • valid - 表单控件有效

    • invalid - 表单控件无效

    • pristine - 表单控件值未改变

    • dirty - 表单控件值已改变

    • touched - 表单控件已被访问过

    • untouched - 表单控件未被访问过

    表单控件上 #userName 和 #userName="ngModel" 这两种方式有什么区别?

    • #userName - 指向 input 表单控件

    • #userName="ngModel" - 指向 NgModel 实例

    ngModelGroup 有什么作用?

    ngModelGroup 指令是 Angular 提供的另一特殊指令,可以对表单输入内容进行分组,方便我们在语义上区分不同性质的输入。例如联系人的信息包括姓名及住址,现在需对姓名和住址进行精细化信息收集,姓名可精细化成姓和名字,地址可精细化成城市、区、街等。此时就可以将姓名及住址进行分组收集,具体如下:

    <form #concatForm = "ngForm">
        <fieldset ngModelGroup="nameGroup" #nameGroup="ngModelGroup">
            <label>姓:</label>
            <input type="text" name="firstname" [(ngModel)]="curContact.firstname" 
                   required> 
              <label>名字:</label>
            <input type="text" name="lastname" [(ngModel)]="curContact.lastname"     
                   required>
        </fieldset>
        <fieldset ngModelGroup="addressGroup" #addressGroup ="ngModelGroup">
            <label>街:</label>
            <input type="text" name="street" [(ngModel)]="curContact.street" required>             <label>区:</label>
            <input type="text" name="zip" [(ngModel)]="curContact.zip" required> 
            <label>城市:</label>
            <input type="text" name="city" [(ngModel)]="curContact.city" required>
        </fieldset>
    </form>

    上述例子分别对联系人的姓名和住址进行分组, ngModelGroup 将姓和名字的表单内容进行包裹组成姓名分组,将城市、区和街道的表单内容进行包裹组成住址分组。此时concatForm.value值为:

    {
      nameGroup: {
        firstname: '',
        lastname: '',
      },
      addressGroup: { 
        street: '', 
        zip: '', 
        city: ''
      } 
    }

    转自:https://segmentfault.com/a/1190000009037539

  • 相关阅读:
    Excel设置下拉选项的方法
    Codeforces Round #218 (Div. 2) (线段树区间处理)
    手动配置S2SH三大框架报错(一)
    一种H.264高清视频的无参考视频质量评价算法(基于QP和跳过宏块数)
    UIWebView的使用
    AFNetworkIng的简单使用
    虚线边框的实现
    iOS实现简单时钟效果
    hdu 3966 Aragorn's Story
    Count on a tree
  • 原文地址:https://www.cnblogs.com/xuepei/p/7922137.html
Copyright © 2020-2023  润新知