Angular4表單(模板驅動表單、響應式表單、自定義表單驗證)

看了兩天官網文檔表單和表單驗證後,才發現以前本身僅僅經過特定的 Angular CSS 類去控制去反饋用戶的操做,如ng-touched等等是多麼的入門,雖說這已經夠用了,可是以前寫的都是在視圖層上寫邏輯,各類判斷,致使後期維護有點困難。趁有時間記錄一下這兩天看了資料的一些心得。javascript

模板驅動表單VS響應式表單

二者均可以經過表單綁定獲取整個表單的值和是否合法eg: myForm.value, myForm.valid等等html

Template-Driven Forms (模板驅動表單) 的特色

  • 使用方便java

  • 適用於簡單的場景react

  • 經過 [(ngModel)] 實現數據雙向綁定json

  • 最小化組件類的代碼segmentfault

  • 不易於單元測試數組

  • 導入FormModule函數

  • 表單綁定經過  #myForm="ngForm"單元測試

Reactive Forms (響應式表單) 的特色

  • 比較靈活測試

  • 適用於複雜的場景

  • 簡化了HTML模板的代碼,把驗證邏輯抽離到組件類中

  • 方便的跟蹤表單控件值的變化

  • 易於單元測試

  • 導入ReactiveFormsModule

  • 表單綁定經過 [formGroup]="myForm"

組件類代碼解釋

Template-Driven Forms (模板驅動表單)

import { Component, AfterViewChecked, ViewChild } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Hero }      from '../shared/hero';
@Component({
  selector: 'hero-form-template2',
  templateUrl: './hero-form-template2.component.html'
})
export class HeroFormTemplate2Component implements AfterViewChecked {

  powers = ['Really Smart', 'Super Flexible', 'Weather Changer'];

  hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What');

  submitted = false;

  onSubmit() {
    this.submitted = true;
  }

  // Reset the form with a new hero AND restore 'pristine' class state
  // by toggling 'active' flag which causes the form
  // to be removed/re-added in a tick via NgIf
  // TODO: Workaround until NgForm has a reset method (#6822)
  active = true;

  addHero() {
    this.hero = new Hero(42, '', '');

    this.active = false;
    setTimeout(() => this.active = true, 0);
  }
  /*組件手動記錄表單對象*/
  heroForm: NgForm;

  /*heroFrom變量是Angular從模板衍生出來的控制模型的引用。 咱們利用@ViewChild來告訴Angular注入這個模型到組件類的currentForm*/
  @ViewChild('heroForm') currentForm: NgForm;

  /*監聽視圖中表格變化,第一次渲染表格對象有多少個值就跑多少次*/
  ngAfterViewChecked() {
    this.formChanged();
  }

  formChanged() {
    /*檢測是否發生變化*/
    if (this.currentForm === this.heroForm) { return; }
    this.heroForm = this.currentForm;
    if (this.heroForm) {
      /*heroForm獲得的是FormModule對象,該對象繼承於AbstractControl抽象類,裏面包含touched等*/
      /*valueChanges返回一個Observal,用於獲取視圖返回的可觀察對象*/
      this.heroForm.valueChanges
        .subscribe(data => this.onValueChanged(data));
    }
  }

  onValueChanged(data?: any) {
    if (!this.heroForm) { return; }
    const form = this.heroForm.form;

    for (const field in this.formErrors) {
      // clear previous error message (if any)
      this.formErrors[field] = '';
      /*heroForm.form.get('name')獲取對應表單裏的鍵值*/
      const control = form.get(field);

      if (control && control.dirty && !control.valid) {
        const messages = this.validationMessages[field];
        for (const key in control.errors) {
          this.formErrors[field] += messages[key] + ' ';
        }
      }
    }
  }

  formErrors = {
    'name': '',
    'power': ''
  };

  validationMessages = {
    'name': {
      'required':      'Name is required.',
      'minlength':     'Name must be at least 4 characters long.',
      'maxlength':     'Name cannot be more than 24 characters long.',
      'forbiddenName': 'Someone named "Bob" cannot be a hero.'
    },
    'power': {
      'required': 'Power is required.'
    }
  };
}

Reactive Forms (響應式表單) 的特色

import { Component, OnInit }                  from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';

import { Hero }                   from '../shared/hero';
import { forbiddenNameValidator } from '../shared/forbidden-name.directive';

@Component({
  ...
})
export class HeroFormReactiveComponent implements OnInit {

  ...
  /**/
  heroForm: FormGroup;
  constructor(private fb: FormBuilder) { }

  ngOnInit(): void {
    /*初始化表單對象*/
    this.buildForm();
  }

  buildForm(): void {
    /*FormBuilder.group是一個用來建立FormGroup的工廠方法*/
    this.heroForm = this.fb.group({
      /*這裏的值接受兩個參數,第一個是值,第二個是檢驗器,若是有多個檢驗器必須用數組表示*/
      'name': [this.hero.name, [
          Validators.required,
          Validators.minLength(4),
          Validators.maxLength(24),
          forbiddenNameValidator(/bob/i)
        ]
      ],
      'alterEgo': [this.hero.alterEgo],
      'power':    [this.hero.power, Validators.required]
    });

    this.heroForm.valueChanges
      .subscribe(data => this.onValueChanged(data));

    this.onValueChanged(); // (re)set validation messages now
  }

  /*與驅動表單上面代碼相似*/
  ...
}

組件視圖層對比

Template-Driven Forms (模板驅動表單)

表組經過ngModelGroup去綁定

<form #templateForm="ngForm" novalidate (ngSubmit)="onSubmit(templateForm)">
    <span>Name</span>
    <label class="user_name">
      <input #uName="ngModel" type="text" name="user_name" [(ngModel)]="user.name" required>
    </label>
    <div ngModelGroup="account">
      <span>QQ</span>
      <label class="user_name">
        <input type="text" name="user_qq" [(ngModel)]="user.account.qq" required>
      </label>
      <span>Password</span>
      <label class="user_name">
        <input type="password" name="user_password" [(ngModel)]="user.account.password" required>
      </label>
    </div>
    <button style="width: 200px; height: 36px; border: 1px solid #ccc; margin-top: 20px;" [disabled]="templateForm.invalid">提交</button>
</form>

Reactive Forms (響應式表單) 的特色

表組經過

formGroupName="address"

-------------------------------------------------------------------

自定義表單驗證

html: 

這裏注意要經過表單.get的方法獲取FormModel的東西,reactiveForm.get('age').touched 或者reactive.controls.age?.touched

<form [formGroup]="reactiveForm" (ngSubmit)="onSubmit()">
  <div class="form-group">
    <label for="name">Name</label>
    <input type="text" id="name" class="form-control" formControlName="name">
  </div>
  <div class="form-group" [ngClass]="{'has-error': (reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && !reactiveForm.get('age').valid }">
    <label class="col-md-2 control-label" for="ageId">年齡</label>

    <div class="col-md-8">
      <input class="form-control" id="ageId" type="number" placeholder="請輸入年齡" formControlName="age" />
      <span class="help-block" *ngIf="(reactiveForm.get('age').touched || reactiveForm.get('age').dirty) && reactiveForm.get('age').errors">
        <span *ngIf="reactiveForm.get('age').errors.range">
            輸入年齡不合法
        </span>
      </span>
    </div>
  </div>
  <p>{{reactiveForm.value | json}}</p>
</form>

ts:

...

@Component({
  ...
})
export class ReactiveFormComponent implements OnInit {

  user: any = {
    name: '',
    age: 0
  };

  reactiveForm: FormGroup;

  constructor(public fb: FormBuilder) { }

  ngOnInit() {
    this.buildForm();
  }

  buildForm(): void {
    this.reactiveForm = this.fb.group({
      'name': [this.user.name, Validators.required],
      'age': [this.user.age, this.ageValidator]    //自定義驗證方法使用
    });

    // this.reactiveForm.valueChanges
    //   .subscribe(data => { console.log(data) });
  }

  /*有參數的驗證器工廠函數寫法,這裏FormControl和下面的AbstractControl都是指向抽象類的屬性和方法都是爲了獲取表單屬性*/
  private ageValidatorParams(min: number, max: number) {
    return (c: FormControl): { [key: string]: any } | null => {
      let age = c.value;
      if (age && (isNaN(age) || age < min || age > max)) {
        return { 'range': true, min: min, max: max };
      }
      return null;
    }
  }
  /*必須返回一個對象,該對象爲錯誤信息的值*/
  private ageValidator(c: AbstractControl): { [key: string]: any } | null {
    let age = c.value;
    if (age && (isNaN(age) || age < 20 || age > 120)) {
      return { 'range': true, min: 20, max: 120 };
    }
    return null;
  }

}

 

 

 

 

參考文獻:

一、Angular官方文檔(表單驗證)

二、模板驅動式表單

三、響應式表單

四、案例狀況比較多的表單驗證

相關文章
相關標籤/搜索