Angular Form-响应式表单

前言

Angular 的 Form 相对于 Angularjs 的来说形式会多一些,我们不仅可以使用像 Angularjs 中的传统写法(模板驱动表单),还可以使用 Angular 中的响应式表单和动态表单,不过本文主要是分享响应式表单。

模板驱动表单

现在我们来看一个简单的例子,这个例子使用了传统的写法。 

在 app.moudle.ts 中引入 FormsModule ,并在 imports 中添加此模块:

import { FormsModule } from '@angular/forms';
@NgModule({
  ......
  imports: [
    ......
    FormsModule
  ]
  ......
})

引入了这个表单模块后,你就可以能访问模板驱动表单的所有特性,比如:ngModel、ngForm

<!-- src/app/post/post-edit.component.html  -->
<div class="edit-page">
  <div class="offset-top-4">
    <span>标题:</span>
    <div>
      <input name="name" class="form-input full-width offset-top-4" type="text" [(ngModel)]="post.name">
    </div>
  </div>
  <div class="offset-top-4">
    <span>选择分类:</span>
    <div class="fx-cell fx-ai-ct offset-top-4">
      <div>
        <select name="categoryId" class="offset-right-4 form-input" style="width:200px" [(ngModel)]="post.categoryId">
          <option *ngIf="!post.categoryId" value="" class="text-gray">--- 请选择 ---</option>
          <option *ngFor="let c of categorys" [value]="c.id" class="text-gray">{{c.name}}</option>
        </select>
      </div>
    </div>
  </div>
  <div class="offset-top-4">
    <span>内容:</span>
    <div>
      <textarea [(ngModel)]="post.content" class="offset-top-4"></textarea>
    </div>
  </div>
  <div class="offset-top-4">
    <label for="">
      公开<input name="publish" type="radio" [value]="1" [(ngModel)]="post.publish">
    </label>
    <label for="">
      不公开<input name="publish" type="radio" [value]="0" [(ngModel)]="post.publish">
    </label>
  </div>
  <div class="text-right offset-top-4">
    <button class="btn btn-blue hand" (click)="savePost()">保存</button>
  </div>
</div>

上面的代码很简单,如果你写过 Angularjs 的话,就应该很有感觉了。

// src/app/post/post-edit.component.ts
import { Component, OnInit } from '@angular/core'

@Component({
  selector: 'post-edit',
  templateUrl: './post-edit.component.html',
  styleUrls: ['./post-edit.component.less']
})
export class PostComponent implements OnInit {
  constructor() { }
  post = {
    name: '',
    content: '',
    categoryId: '',
    publish: 0
  }
  categorys = [
    { id: 1, name: "JavaScript", },
    { id: 2, name: "Angular", },
    { id: 3, name: "Vue", }
  ]
  savePost () {
    if(!this.post.name){
      console.log('标题不能为空')
      return
    }
    if(!this.post.categoryId){
      console.log('请选择分类')
      return
    }
    if(!this.post.content){
      console.log('内容不能为空')
      return
    }
    console.log('新增成功')
  }
}

以前在 angularjs 中我们一般都会像上面这样写表单,并在提交前作验证,符合则提交。

但现在到了 Angular 时代,我们可以换一种方式来书写表单。

响应式表单

我们可以使用 Angular 为我们提供的模块来实现响应式表单。

要想使用响应式表单,我们得先引入相关模块,在 app.moudle.ts 中引入 ReactiveFormsModule ,并在 imports 中添加此模块:

import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
  ......
  imports: [
    ......
    ReactiveFormsModule
  ]
  ......
})

成功引入之后我们就可以在页面中使用响应式表单的相关属性了。

<!-- src/app/post/post-edit.component.html  -->
<div class="edit-page">
  <form [formGroup]="postForm" (ngSubmit)="savePost()">
    <div class="offset-top-4">
      <span>标题:</span>
      <div>
        <input class="form-input full-width offset-top-4" type="text" formControlName="name">
      </div>
      <div class=“red” *ngIf="name.invalid && (name.dirty || name.touched)">
        <span *ngIf="name.errors.required">请输入标题</span>
        <span *ngIf="name.errors.maxlength">标题不得超过50个字符</span>
      </div>
    </div>
    <div class="offset-top-4">
      <span>选择分类:</span>
      <div class="fx-cell fx-ai-ct offset-top-4">
        <div>
          <select name="categoryId" class="offset-right-4 form-input" style="width:200px" formControlName="categoryId">
            <option value="" class="text-gray">--- 请选择 ---</option>
            <option *ngFor="let c of categorys" [value]="c.id" class="text-gray">{{c.name}}</option>
          </select>
        </div>
      </div>
      <div class=“red” *ngIf="categoryId.invalid && (categoryId.dirty || categoryId.touched)">
        <span *ngIf="categoryId.errors.required">请选择分类</span>
      </div>
    </div>
    <div class="offset-top-4">
      <span>内容:</span>
      <div>
        <textarea formControlName="content" class="offset-top-4"></textarea>
      </div>
      <div class=“red” *ngIf="content.invalid && (content.dirty || content.touched)">
        <span *ngIf="content.errors.required">请输入内容</span>
        <span *ngIf="content.errors.minlength">内容不得少于50个字符</span>
      </div>
    </div>
    <div class="offset-top-4">
      <label for="">
        公开<input name="publish" type="radio" [value]="1" formControlName="publish">
      </label>
      <label for="">
        不公开<input name="publish" type="radio" [value]="0" formControlName="publish">
      </label>
    </div>
    <div class="text-right offset-top-4">
      <button class="btn btn-blue hand" type="submit" [disabled]="!postForm.valid">保存</button>
    </div>
  </form>
</div>

其中需要注意的是 minlength 和 maxlength 这两个是全小写的。

// src/app/post/post-edit.component.ts
import { Component } from '@angular/core'
import { FormGroup, FormControl, Validators } from '@angular/forms';

@Component({
  selector: 'post-edit',
  templateUrl: './post-edit.component.html',
  styleUrls: ['./post-edit.component.less']
})
export class PostEditComponent {
  constructor() { }
  categorys = [
    { id: 1, name: "JavaScript", },
    { id: 2, name: "Angular", },
    { id: 3, name: "Vue", }
  ]
  postForm = new FormGroup({
    name: new FormControl('', [Validators.required,Validators.maxLength(50)]),
    categoryId: new FormControl('', Validators.required),
    content: new FormControl('', [Validators.required,Validators.minLength(50)]),
    publish: new FormControl(0, Validators.required)
  })
  get name (){
    return this.postForm.get('name')
  }
  get categoryId (){
    return this.postForm.get('categoryId')
  }
  get content (){
    return this.postForm.get('content')
  }
  savePost () {
    if(this.postForm.valid){
      console.log(this.postForm.value);
      console.log('新增成功')
    }
  }
}

这里还需要注意的是,模板中使用的name、caetegoryId、content 属性都是在 PostEditComponent 类中以 get 的方式定义好了的,这里相当于监听这几个属性,只有有读取操作的都会返回 this.postForm 中相应属性所对应的值。

虽然响应式的代码看起来比模式驱动表单的实现方式代码会多一些,但响应式表单给我们提供了更多的可能,比如提供了一些字段验证规则及验证失败状态反馈。