[Angular 基础] - 表单:模板驱动表单

[Angular 基础] - 表单:模板驱动表单

之前的笔记:


Angular 内置两种表单的支持,这篇写的就是第一种,即模板驱动表单 (Template-Driven Form)

Template-Driven Form 的实现比较简单,Angular 自身会生成和提供对应的表单控制和管理状态,对于开发者来说,实现相对而言更加简单,因此更加适合简单的表单实现

做一个平行对比,React 有 uncontrolled form & controlled form,Angular 有一个 Template-Driven Form 和 Reactive form,虽然这么看起来两个实现似乎挺像的,不过本质上还是有些区别的

对于 React 的 uncontrolled form,它本质上是让 HTML 去进行处理,然后可以通过 refs 去获得 HTML 的值,React 不会对其进行太多的干预;对比 Angular 的 Template-Driven Form,Angular 本身通过 绑定 对状态进行了接管

开始

下面是一个表单:

html 复制代码
<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <form>
        <div id="user-data">
          <div class="form-group">
            <label for="username">Username</label>
            <input type="text" id="username" class="form-control" />
          </div>
          <button class="btn btn-default" type="button">
            Suggest an Username
          </button>
          <div class="form-group">
            <label for="email">Mail</label>
            <input type="email" id="email" class="form-control" />
          </div>
        </div>
        <div class="form-group">
          <label for="secret">Secret Questions</label>
          <select id="secret" class="form-control">
            <option value="pet">Your first Pet?</option>
            <option value="teacher">Your first teacher?</option>
          </select>
        </div>
        <button class="btn btn-primary" type="submit">Submit</button>
      </form>
    </div>
  </div>
</div>

表单渲染如下:

相别于直接使用 JS+HTML 的方式实现表单,使用 Angular 这里没有添加 action 这一 action,这一步会让 Angular 去安排。

这一点在之前也提过,需要实现这一点,也需要在 app.module.ts 中导入 FormsMorule

绑定控制

这里主要使用 ngModelname="" 绑定所有输入:

html 复制代码
<label for="username">Username</label>
<input type="text" id="username" class="form-control" ngModel name="username" />

<label for="email">Mail</label>
<input type="email" id="email" class="form-control" ngModel name="email" />

<label for="secret">Secret Questions</label>
<select id="secret" class="form-control" ngModel name="secret">
  <option value="pet">Your first Pet?</option>
  <option value="teacher">Your first teacher?</option>
</select>

我这里省略了其他部分的代码,只留下了对应的 labelinput

⚠️:name 是一个 HTML attribute,这个实现本质上跟 angular 没什么关系。但是在 Template-Driven Form 中它的作用非常的重要。它会用来创建一个 FormControl 的实例对象,对 ngModel 进行自动绑定,这也是为什么这里的 ngModel 使用语法和 2 way binding 里的不一样。可以说,没有 name,那么 Angular 就无法正确识别对应的 ``FormControl`,也无法接管对应的 onChange 和 validation

提交表单 AKA 获取提交的值

这里提交表单的做法不是之前实现的那样,在 submit button 上绑定 onclick 事件,而是使用 angular 提供的 directive 去实现------这也是为什么这种实现叫 Template-Driven Form,代码修改如下:

  • V 层

    html 复制代码
    <form (ngSubmit)="onSubmit(f)" #f="ngForm">
      <!-- 其余不变 -->
    </form>
  • VM 层

    typescript 复制代码
    onSubmit(form: NgForm) {
        console.log('submitted');
        console.log(form);
    }

输出结果如下:

注意这里的 local reference 用法,它绑定了一个特殊的 ngForm directive,因此传到 onSubmit 中的值就是 NgForm。默认情况下它的值是当前 HTML 元素类型,表单对应的元素类型是 HTMLFormElement

通过 NgForm 就可以轻松的获取当前表单的值、控制(即 FormControl)、验证等。这些值可以杯统称为当前表单的 状态

验证

这里主要是通过 angular 提供的验证搭配 HTML5 的验证进行实现,代码修改如下:

html 复制代码
<div class="form-group">
  <label for="username">Username</label>
  <input
    type="text"
    id="username"
    class="form-control"
    ngModel
    name="username"
    required
  />
</div>
<input
  type="email"
  id="email"
  class="form-control"
  ngModel
  name="email"
  required
  email
/>

这里主要用了 angular 提供的 requiredemail 这两个 validators。这个时候表单还是可以继续提交的,输出结果如下:

可以看到,当前 NgFormsubmittedtrueinvalid 也是 true

这个时候就可以利用一下这个 invalid 属性:

html 复制代码
<button class="btn btn-primary" type="submit" [disabled]="f.invalid">
  Submit
</button>

这样可以阻止用户在表单验证未通过的情况下,点击 submit 事件:

修改报错样式

这里主要通过 CSS 实现,修改代码如下:

css 复制代码
input.ng-invalid.ng-touched,
select.ng-invalid.ng-touched {
  border: 1px solid red;
}

实现效果如下:

这里也是通过 angular 提供的 class 进行的实现。angular 会在对应的元素上添加对应的状态,touched 指的是用户是否触碰(focus)过当前元素。

报错信息

这里使用 ngIf 搭配对应的 directive 进行报错信息的渲染,代码修改如下:

html 复制代码
<div class="form-group">
  <label for="email">Mail</label>
  <input
    type="email"
    id="email"
    class="form-control"
    ngModel
    name="email"
    autocomplete="off"
    required
    email
    #email="ngModel"
  />
  <span class="help-block" *ngIf="email.invalid && email.touched"
    >Please enter a valid email!</span
  >
</div>

效果如下:

⚠️:这里依旧使用 local reference+绑定 directive 的方法实现,不过这里绑定的是 ngModel 而不是 ``NgForm`

设置默认值

这里主要就是设置一下默认值,使用的是 property binding,代码如下:

html 复制代码
<div class="form-group">
  <label for="secret">Secret Questions</label>
  <select id="secret" class="form-control" [ngModel]="'pet'" name="secret">
    <option value="pet">Your first Pet?</option>
    <option value="teacher">Your first teacher?</option>
  </select>
</div>

实现效果如下:

⚠️:这里使用的是 property binding,即 [ngModel] 提供默认值,而不是提供 2-way binding------在有需求的情况下还是可以使用 2-way binding 的。使用 property binding 并不会修改 VM 层中的数据。

👀:也可以在 VM 层实现一个变量,而不是直接将值写到 V 层中

组合 form control

这可以将一些数据组合在一起,比如说地址的组合通常为省+市+具体地址+邮编才能组合成一个完整的地址,实现方法如下:

html 复制代码
<div id="user-data" ngModelGroup="userData">
  <div class="form-group">
    <label for="username">Username</label>
    <input
      type="text"
      id="username"
      class="form-control"
      ngModel
      name="username"
      required
    />
  </div>
  <button class="btn btn-default" type="button">Suggest an Username</button>
  <div class="form-group">
    <label for="email">Mail</label>
    <input
      type="email"
      id="email"
      class="form-control"
      ngModel
      name="email"
      autocomplete="off"
      required
      email
      #email="ngModel"
    />
    <span class="help-block" *ngIf="email.invalid && email.touched"
      >Please enter a valid email!</span
    >
  </div>
</div>

效果如下:

在这里插入图片描述

⚠️:这里的语法为 ngModelGroup="string-value"

👀:这里同样可以添加 local reference,如: #userData="ngModelGroup" 这样就可以对整个 group 进行提示,如:

html 复制代码
<p *ngIf="userData.invalid && userData.touched">User Data is invalid!</p>

这个报错信息只有在用户提供了正确的用户名和邮箱之后才会消失:

radio button

这里没有什么特别的地方,不过可以搭配 ngFor 让整个实现变得简单一些。

假设 VM 层有一个数组包含 [male, female] 这两个数据,V 层渲染如下:

html 复制代码
<div class="form-group" *ngFor="let gender of genders">
  <label for=""></label>
  <input type="radio" name="gender" ngModel [value]="gender" />
  {{ gender }}
</div>

结果如下:

使用 @ViewChild 获取表单

这里补充另一种可以获取表单的方式,就是通过 @ViewChild 进行绑定

@ViewChild 的使用在之前的笔记提过: [Angular 基础] - 视图封装 & 局部引用 & 父子组件中内容传递

使用方法如下:

typescript 复制代码
export class AppComponent {
  @ViewChild('f') signupForm: NgForm;

  suggestUserName() {
    const suggestedName = 'Superuser';
  }

  // onSubmit(form: NgForm) {
  //   console.log('submitted');
  //   console.log(form);
  // }

  onSubmit(f: NgForm) {
    console.log(this.signupForm);
  }
}

输出结果如下:

使用 @ViewChild

使用 @ViewChild 可以提供更加灵活的使用,比如说 UI 上的一个 Suggest an Username 按钮,我想要点一下这里就生成一个用户名时,就可以使用 @ViewChild 去实现。

这里会提供两种写法,根据业务需求去使用,具体使用方式在注释里:

  • VM 层

    typescript 复制代码
    suggestUserName() {
      const suggestedName = 'Superuser';
      // 这里必须提供所有的值,否则会报错,因此就有可能会重写所有值的问题
      this.signupForm.setValue({
        userData: {
          username: suggestedName,
          email: '',
        },
        secret: 'pet',
        gender: 'male',
      });
    
      // 这里就是打个补丁,不会重写所有的值
      this.signupForm.form.patchValue({
        userData: {
          username: suggestedName,
        },
      });
    }

    这里直接使用 setValue 去设置一个与提交格式对应的表单

  • V 层

    html 复制代码
    <button class="btn btn-default" type="button" (click)="suggestUserName()">
      Suggest an Username
    </button>

    这里绑定 click 事件

这里的效果是重写所有值,动图如下:

⚠️:还是可以使用 event handler+2-way data binding 去实现的,只是对于 Template-Driven Form 来说,这种实现方法最方便

使用表单数据

这里还是需要创建一个新的对象用来 match 提交的表单数据,从而完成渲染,实现如下:

  • V 层

    html 复制代码
    <div class="row" *ngIf="f.submitted">
      <div class="col-xs-12">
        <h3>Your Data</h3>
        <p>Username: {{ user.username }}</p>
        <p>Email: {{ user.email }}</p>
        <p>Secret Question: {{ user.secret }}</p>
        <p>Gender: {{ user.gender }}</p>
      </div>
    </div>
  • VM 层

    typescript 复制代码
    onSubmit(f: NgForm) {
        this.user.username = f.value.userData.username;
        this.user.email = f.value.userData.email;
        this.user.secret = f.value.secret;
        this.user.gender = f.value.gender;
    }

渲染如下:

这里的逻辑是只有在提交的时候会对数据进行赋值,所以直接检查 f.submitted 也不会造成修改表单数据就让下面的数据区域重新渲染的结果。

重置表单

这里就一行代码:

typescript 复制代码
this.signupForm.reset();

效果如下:

⚠️:reset 中也可以传值,用法和 setValue 类似

reference

相关推荐
bysking16 分钟前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓32 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
fg_41135 分钟前
无网络安装ionic和运行
前端·npm
理想不理想v37 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
酷酷的阿云1 小时前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:137971205871 小时前
web端手机录音
前端
齐 飞1 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb
神仙别闹1 小时前
基于tensorflow和flask的本地图片库web图片搜索引擎
前端·flask·tensorflow
aPurpleBerry2 小时前
JS常用数组方法 reduce filter find forEach
javascript
GIS程序媛—椰子2 小时前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js