iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 6
2

Angular 框架仰賴著 HTML 5 強大的功能,本身已經包括雙向綁定、變化跟踪、驗證、錯誤處理等功能。
可以處理:

  • Component 和 Template 構建表單
  • 雙向綁定[(ngModel)]讀寫表單表。
  • ngControl追蹤表單狀態和有效性。
  • 根據ngControl改變 CSS。
  • 顯示驗證錯誤消息並啟用/禁止表單。
  • 使用本地模板變量共享控制間的信息。

簡單版

// app.simpleform.html

 <!-- 這邊有用 Bootstrap CSS 唷! -->
 <div class="jumbotron">
    <h2>Template Driven Form</h2>
     <!-- 這邊我們宣告一個區域變數 "form" 並讓它變成 ngForm 的實例。這樣子可以讓 "form" 使用 FormGroup的 API,我們就能夠用 ngSubmit 送出 form.value 表格的值。-->
    <form #form="ngForm" (ngSubmit)="submitForm(form.value)">
      <div class="form-group">
        <label>First Name:</label>
        <!-- 這邊的 ngModal 是單向綁定,只會送資料回去。當然我們也可以用 [(ngModal)] 來雙向綁定表格的值 -->
        <input type="text" class="form-control" placeholder="John" name="firstName" ngModel required>
      </div>
      <div class="form-group">
        <label>Last Name</label>
        <input type="text" class="form-control" placeholder="Doe" name="lastName" ngModel required>
      </div>
      <div class="form-group">
        <label>Gender</label>
      </div>
      <!-- Radio 和 checkbox 跟一般的 HTML用法差不多 -->
      <div class="radio">
        <label>
          <input type="radio" name="gender" value="Male" ngModel>
          Male
        </label>
      </div>
      <div class="radio">
        <label>
          <input type="radio" name="gender" value="Female" ngModel>
          Female
        </label>
      </div>
      <div class="form-group">
        <label>Activities</label>
      </div>
      <label class="checkbox-inline">
        <input type="checkbox" value="hiking" name="hiking" ngModel> 
        Hiking
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="swimming" name="swimming" ngModel> 
        Swimming
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="running" name="running" ngModel> 
        Running
      </label>
      <div class="form-group">
        <button type="submit" class="btn btn-default">Submit</button>
      </div>
    </form>
  </div>
// app.simpleform.ts

import { Component } from '@angular/core';

@Component({
  selector: 'simple-form',
  templateUrl: 'app.simpleform.html'
})
export class SimpleFormComponent {
  //這邊設定提交後要幹嘛,目前只是 Console.log 出資料而已
  submitForm(form: any): void{
    console.log('Form Data: ');
    console.log(form);
  }
}

程式碼就不多解釋,都有加註解,此外 required 會強制要求使用者輸入,否則就會無法提交。
上面的範例會長得像這樣

複雜一點點版本

好的例子勝過萬言

// app.complexform.ts

import { Component } from '@angular/core';
// 需要引入一些程式庫
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'complex-form',
  templateUrl : './app.complexform.html'
})
export class ComplexFormComponent {
  // 新建一個 FormGroup 物件
  complexForm : FormGroup;

  // 建構一個實例 FormBuilder
  constructor(fb: FormBuilder){
    // 用 FormBuilder 來製造我們的表格
    this.complexForm = fb.group({
      // 定義表格的預設值
      'firstName' : '',
      'lastName': '',
      'gender' : 'Female',
      'hiking' : false,
      'running' : false,
      'swimming' : false
    })
  }

  // 提交的執行的 function
  submitForm(value: any): void {
    console.log('Reactive Form Data:', value);
  }
}
//app.complexform.html

<div class="jumbotron">
    <h2>Data Driven (Reactive) Form</h2>
    <!-- 現在我們不宣告區域變數了,改用formGroup 這個指令,並將它定義成 complexForm 物件。 complexForm 將會控制表格所有的變數 -->
    <form [formGroup]="complexForm" (ngSubmit)="submitForm(complexForm.value)">
      <div class="form-group">
        <label>First Name:</label>
        <!-- 不用 ngModel 而改用 formControl 指令可以同步 input 到 complexForm 物件。現在也不需要 name 屬性了,但還是可以選擇加入 -->
        <input class="form-control" type="text" placeholder="John" [formControl]="complexForm.controls['firstName']">
      </div>
      <div class="form-group">
        <label>Last Name</label>
        <input class="form-control" type="text" placeholder="Doe" [formControl]="complexForm.controls['lastName']">
      </div>
      <div class="form-group">
        <label>Gender</label>
      </div>
      <div class="radio">
        <label>
          <input type="radio" name="gender" value="Male" [formControl]="complexForm.controls['gender']">
          Male
        </label>
      </div>
      <div class="radio">
        <label>
          <!-- radio 和 checkbox 用法也一樣 -->
          <input type="radio" name="gender" value="Female" [formControl]="complexForm.controls['gender']">
          Female
        </label>
      </div>
      <div class="form-group">
        <label>Activities</label>
      </div>
      <label class="checkbox-inline">
        <input type="checkbox" value="hiking" name="hiking" [formControl]="complexForm.controls['hiking']"> Hiking
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="swimming" name="swimming" [formControl]="complexForm.controls['swimming']"> Swimming
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="running" name="running" [formControl]="complexForm.controls['running']"> Running
      </label>
      <div class="form-group">
        <button type="submit" class="btn btn-default">Submit</button>
      </div>
    </form>
  </div>

上面複雜一點點的範例,更貼切 Angular 的用法,跑出來會長這樣。
也可以看到 Female 為預設值。

表格驗證

Angular 2 的表格驗證支援模板控制以及組件控制,但是透過組件控制在表格驗證上有更大的彈性。詳細可以參考 Angular 2 docs

我們從複雜板來加入表格驗證功能

//app.validationform.ts

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

@Component({
  selector: 'form-validation',
  template : 'app.validationform.html'
})
export class FormValidationComponent {
  complexForm : FormGroup;

  constructor(fb: FormBuilder){
    this.complexForm = fb.group({
      // 表示一定要輸入
      'firstName' : [null, Validators.required],
      // 表示一定要輸入,而且最短為5個字元,最多為10個字元。有多個規則時用陣列包住。
      'lastName': [null,  Validators.compose([Validators.required, Validators.minLength(5), Validators.maxLength(10)])],
      'gender' : [null, Validators.required],
      'hiking' : [false],
      'running' : [false],
      'swimming' : [false]
    })
    
    // 用來觀察表格元素的變化
    console.log(this.complexForm);
    this.complexForm.valueChanges.subscribe( (form: any) => {
      console.log('form changed to:', form);
    }
    );
  }

  // 提交執行的程式
  submitForm(value: any){
    console.log(value);
  }
}
  //app.validationform.html
  
  <div class="jumbotron">
    <h2>Form with Validations</h2>
    <form [formGroup]="complexForm" (ngSubmit)="submitForm(complexForm.value)">
      <!-- 填寫的值必須有效 且 可以讓使用者點擊過一次之後才會顯示 Error ( ngClass 使顏色改變) -->
      <div class="form-group" [ngClass]="{'has-error':!complexForm.controls['firstName'].valid && complexForm.controls['firstName'].touched}">
        <label>First Name:</label>
        <input class="form-control" type="text" placeholder="John" [formControl]="complexForm.controls['firstName']">
        <!-- .hasError('why')可以在發生填寫錯誤時告訴使用者為甚麼 Error -->
        <div *ngIf="complexForm.controls['firstName'].hasError('required') && complexForm.controls['firstName'].touched" class="alert alert-danger">You must include a first name.</div>
      </div>
      <div class="form-group" [ngClass]="{'has-error':!complexForm.controls['lastName'].valid && complexForm.controls['lastName'].touched}">
        <label>Last Name</label>
        <input class="form-control" type="text" placeholder="Doe" [formControl]="complexForm.controls['lastName']">
        <!-- .hasError('required')代表必須要填 -->
        <div *ngIf="complexForm.controls['lastName'].hasError('required') && complexForm.controls['lastName'].touched" class="alert alert-danger">You must include a last name.</div>
        <!-- .hasError('minlength')代表太短 -->
        <div *ngIf="complexForm.controls['lastName'].hasError('minlength') && complexForm.controls['lastName'].touched" class="alert alert-danger">Your last name must be at least 5 characters long.</div>
        <div *ngIf="complexForm.controls['lastName'].hasError('maxlength') && complexForm.controls['lastName'].touched" class="alert alert-danger">Your last name cannot exceed 10 characters.</div>
      </div>
      <div class="form-group">
        <label>Gender</label>
        <!-- Radio 無法上色,所以只用提示字串 -->
        <div class="alert alert-danger" *ngIf="!complexForm.controls['gender'].valid && complexForm.controls['gender'].touched">You must select a gender.</div>
      </div>
      <div class="radio">
        <label>
          <input type="radio" name="gender" value="Male" [formControl]="complexForm.controls['gender']">
          Male
        </label>
      </div>
      <div class="radio">
        <label>
          <input type="radio" name="gender" value="Female" [formControl]="complexForm.controls['gender']">
          Female
        </label>
      </div>
      <div class="form-group">
        <label>Activities</label>
      </div>
      <label class="checkbox-inline">
        <input type="checkbox" value="hiking" name="hiking" [formControl]="complexForm.controls['hiking']"> Hiking
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="swimming" name="swimming" [formControl]="complexForm.controls['swimming']"> Swimming
      </label>
      <label class="checkbox-inline">
        <input type="checkbox" value="running" name="running" [formControl]="complexForm.controls['running']"> Running
      </label>
      <div class="form-group">
        <button type="submit" class="btn btn-primary" [disabled]="!complexForm.valid">Submit</button>
      </div>
    </form>
  </div>


上一篇
[Day 05] Angular2 模板與綁定--互動的根本
下一篇
[Day 07] Angular 2 PIPES(通道)
系列文
Angular 2 之 30 天邁向神乎其技之路31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
LeeBoy
iT邦新手 4 級 ‧ 2018-01-25 20:24:06

在第二部分,會報一個錯誤:
Can't bind to 'formGroup' since it isn't a known property of 'form'

參考這篇 issue #14288 解決:

  1. in app.module.ts,要同時引入 FormsModule, ReactiveFormsModule
 import { FormsModule, ReactiveFormsModule } from '@angular/forms';`

@NgModule({
  declarations: [
    AppComponent,
  ]
  imports: [
    FormsModule,
    ReactiveFormsModule,
    AuthorModule,
],
...
  1. 如果是 submodule 要再重複引用一次,app.module.ts 並不會讓 formGroup 變成全域 directive

  2. 如果沒有使用 submodule ,component 寫法同你的教學即可。

微中子 iT邦新手 4 級 ‧ 2018-01-29 00:00:57 檢舉

謝謝回報!
這是版本 2,跟最新版(5)也有點脫節了,沒有打算再繼續維護這篇文章 :P

我要留言

立即登入留言