細說 Angular 2+ 的表單(一):模板驅動型表單

細說 Angular 2+ 的表單(二):響應式表單

摘要

在企業應用開發時,表單是一個躲不過去的事情,和面向消費者的應用不同,企業領域的開發中,表單的使用量是驚人的。這些表單的處理其實是一個挺復雜的事情,比如有的是涉及到多個 Tab 的表單,有的是向導形式多個步驟的,各種復雜的驗證邏輯和時不時需要彈出的對話框等等。筆者試圖在這一系列文章中對 Angular 中的表單處理做一個相對完整的梳理。

Angular 中提供兩種類型的表單處理機制,一種叫模版驅動型(Template Driven)的表單,另一種叫模型驅動型表單( Model Driven ),這后一種也叫響應式表單 ( Reactive Forms ),由于模版驅動中有一個 ngModel 的指令,容易和這里說的模型驅動混淆,所以在我們的文章中叫后一種說法:響應式表單。

第一篇主要介紹模版驅動型的表單。

模版驅動的表單

模版驅動的表單和 AngularJS 對于表單的處理類似,把一些指令(比如 ngModel )、數據值和行為約束(比如 requireminlength 等等)綁定到模版中(模版就是組件元數據 @Component 中定義的那個 template ),這也是模版驅動這個叫法的來源。總體來說,這種類型的表單通過綁定把很多工作交給了模版。

模版驅動的例子

還是用例子來說話,比如我們有一個用戶注冊的表單,用戶名就是 email ,還需要填的信息有:住址、密碼和重復密碼。這個應該是比較常見的一個注冊時需要的信息了。那么我們第一步來建立領域模型:

// src/app/domain/index.ts
export interface User {
  // 新的用戶id一般由服務器自動生成,所以可以為空,用 ? 標示
  id?: string; 
  email: string;
  password: string;
  repeat: string;
  address: Address;
}

export interface Address {
  province: string; // 省份
  city: string; // 城市
  area: string; // 區縣
  addr: string; // 詳細地址
}

接下來我們建立模版文件,一個最簡單的 HTML 模版,先不增加任何的綁定或事件處理:

<!-- template-driven.component.html -->
<form novalidate>
  <label>
    <span>電子郵件地址</span>
    <input
      type="text"
      name="email"
      placeholder="請輸入您的 email 地址">
  </label>
  <div>
    <label>
      <span>密碼</span>
      <input
        type="password"
        name="password"
        placeholder="請輸入您的密碼">
    </label>
    <label>
      <span>確認密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請再次輸入密碼">
    </label>
  </div>
  <div >
    <label>
      <span>省份</span>
      <select name="province">
        <option value="">請選擇省份</option>
      </select>
    </label>
    <label>
      <span>城市</span>
      <select name="city">
        <option value="">請選擇城市</option>
      </select>
    </label>
    <label>
      <span>區縣</span>
      <select name="area">
        <option value="">請選擇區縣</option>
      </select>
    </label>
    <label>
      <span>地址</span>
      <input type="text" name="addr">
    </label>
  </div>
  <button type="submit">注冊</button>
</form>

渲染之后的效果就像下面這樣:

簡單的Form
簡單的Form

數據綁定

對于模版驅動型的表單處理,我們首先需要在對應的模塊中引入 FormsModule ,這一點千萬不要忘記了。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from "@angular/forms";
import { TemplateDrivenComponent } from './template-driven/template-driven.component';

@NgModule({
  imports: [
    CommonModule,
    FormsModule
  ],
  exports: [TemplateDrivenComponent],
  declarations: [TemplateDrivenComponent]
})
export class FormDemoModule { }

進行模版驅動類型的表單處理的一個必要步驟就是建立數據的雙向綁定,那么我們需要在組件中建立一個類型為 User 的成員變量并賦初始值。

// template-driven.component.ts
// 省略元數據和導入的類庫信息
export class TemplateDrivenComponent implements OnInit {

  user: User = {
    email: '',
    password: '',
    repeat: '',
    address: {
      province: '',
      city: '',
      area: '',
      addr: ''
    }
  };
  // 省略其他部分
}

有了這樣一個成員變量之后,我們在組件模版中就可以使用 ngModel 進行綁定了。

令人困惑的 ngModel

我們在 Angular 中可以使用三種形式的 ngModel 表達式: ngModel , [ngModel][(ngModel)]。但無論那種形式,如果你要使用 ngModel 就必須為該控件(比如下面的 input )指定一個 name 屬性,如果你忘記添加 name 的話,多半你會看到下面這樣的錯誤:

ERROR Error: Uncaught (in promise): Error: If ngModel is used within a form tag, either the name attribute must be set or the form control must be defined as 'standalone' in ngModelOptions.

ngModel 和 FormControl

假如我們使用的是 ngModel ,沒有任何中括號小括號的話,這代表著我們創建了一個 FormControl 的實例,這個實例將會跟蹤值的變化、用戶的交互、驗證狀態以及保持視圖和領域對象的同步等工作。

<input
  type="text"
  name="email"
  placeholder="請輸入您的 email 地址"
  ngModel>

如果我們將這個控件放在一個 Form 表單中, ngModel 會自動將這個 FormControl 注冊為 Form 的子控件。下面的例子中我們在 <form> 中加上了 ngForm 指令,聲明這是一個 Angular 可識別的表單,而 ngModel 會將 <input> 注冊成表單的子控件,這個子控件的名字就是 email,而且 ngModel 會基于這個子控件的值去綁定表單的的值,這也是為什么需要顯式聲明 name 的原因。

其實在我們導入 FormsModule 的時候,所有的 <form> 標簽都會默認的被認為是一個 NgForm ,因此我們并不需要顯式的在標簽中寫 ngForm 這個指令。

<!-- ngForm 并不需要顯示聲明,任何 <form> 標簽默認都是 ngForm -->
<form novalidate ngForm>
  <input
    type="text"
    name="email"
    placeholder="請輸入您的 email 地址"
    ngModel>
</form>

這一切現在都是不可見的,所以大家可能還是有些困惑,那么下面我們將其“可視化”,這需要我們引用一下表單對象,所以我們使用 #f="ngForm" 以便我們可以在模版中輸出表單的一些特性。

<!-- 使用 # 把表單對象導出到 f 這個可引用變量中 -->
<form novalidate #f="ngForm">
  ...
</form>
<!-- 將表單的值以 JSON 形式輸出 -->
{{f.value | json}} 

這時如果我們在 email 中輸入 sss ,可以看到下圖的以 JSON 形式出現的表單值:

控件的輸入值同步到了表單的值中
控件的輸入值同步到了表單的值中

單向數據綁定

那么接下來,我們看看 [ngModel] 有什么用?如果我們想給控件設置一個初始值怎么辦呢,這時就需要進行一個單向綁定,方向是從組件到視圖。我們可以做的是在初始化 User 的時候,將 email 屬性設置成 wang@163.com

user: User = {
    email: 'wang@163.com',
    ...
  };

而且在模版中使用 [ngModel]="user.email" 進行單向綁定,這個語法其實和普通的屬性綁定是一樣的,用中括號標示這是一個要進行數據綁定的屬性,等號右邊是需要綁定的值(這里是 user.email )。那么我們就可以得到下面這樣的輸出了, email 的初始值被綁定成功!

單向數據綁定
單向數據綁定

雙向數據綁定

但上面的例子存在一個問題,數據的綁定是單向的,也就是說,在輸入框進行輸入的時候,我們的 user 的值不會隨之改變的。為了更好的說明,我們將 user 和 表單的值同時輸出

<div>
  <span>user: </span> {{user | json}}
</div>
<div>
  <span>表單:</span> {{f.value | json}}
</div>

此時我們將默認的電子郵件改成 wang@gmail.com 的話,表單的值是改變了,但 user 并未改變。

輸入的值影響了表單,但不會影響領域對象
輸入的值影響了表單,但不會影響領域對象

如果我們希望的是在輸入時,這個輸入的值也反向的影響我們的 user 對象的值的話,那就需要用到雙向綁定了,也就是 [(ngModel)] 需要上場了。

表單和領域對象的值保持了同步
表單和領域對象的值保持了同步

無論如何,這個 [()] 表達真是很奇怪的樣子,其實這個表達是一個語法糖。只要我們知道下面的兩種寫法是等價的,我們就會很清楚的理解了:用這個語法糖你就不用既寫數據綁定又寫事件綁定了。

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

ngModelGroup 是什么鬼?

如果我們仔細觀察上面的輸出的話,會發現一個問題: user 中是有一個嵌套對象 address 的,而表單中沒有嵌套對象的。如果要實現表單中的結構和領域對象的結構一致的話,我們就得請出 ngModelGroup 了。ngModelGroup 會創建并綁定一個 FormGroup 到該 DOM 元素。 FormGroup 又是什么呢?簡單來說,是一組 FormControl。

  <!-- 使用 ngModelGroup 來創建并綁定 FormGroup  -->
  <div ngModelGroup="address">
    <label>
      <span>省份</span>
      <select name="province" (change)="onProvinceChange()" [(ngModel)]="user.address.province">
        <option value="">請選擇省份</option>
        <option [value]="province" *ngFor="let province of provinces">{{province}}</option>
      </select>
    </label>
    <!-- 省略其他部分 -->
  </div>

這樣的話,我們再來看一下輸出,現在就完全一致了:

表單和領域對象的結構也完全一致了
表單和領域對象的結構也完全一致了

數據驗證

模版驅動型的表單的驗證也是主要由模版來處理的,在看怎么使用之前,需要界定一下驗證規則:

  • 三個必填項: email, passwordrepeat
  • email 的形式需要符合電子郵件的標準
  • passwordrepeat 必須一致

當然除了這幾個規則,我們還希望在表單未驗證通過時提交按鈕是不可用的。

<form novalidate #f="ngForm">
  <label>
    <span>電子郵件地址</span>
    <input
      type="text"
      name="email"
      placeholder="請輸入您的 email 地址"
      [ngModel]="user.email"
      required
      pattern="([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}">
  </label>
  <div>
    <label>
      <span>密碼</span>
      <input
        type="password"
        name="password"
        placeholder="請輸入您的密碼"
        [(ngModel)]="user.password"
        required
        minlength="8">
    </label>
    <label>
      <span>確認密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請再次輸入密碼"
        [(ngModel)]="user.repeat"
        required
        minlength="8">
    </label>
  </div>
  <!-- 省略其他部分 -->
  <button type="submit" [disabled]="f.invalid">注冊</button>
</form>
<div>

Angular 中有幾種內建支持的驗證器( Validators )

  • required - 需要 FormControl 有非空值
  • minlength - 需要 FormControl 有最小長度的值
  • maxlength - 需要 FormControl 有最大長度的值
  • pattern - 需要 FormControl 的值可以匹配正則表達式

如果我們想看到結果的話,我們可以在模版中加上下面的代碼,將錯誤以 JSON 形式輸出即可。

<div>
  <span>email 驗證:</span> {{f.controls.email?.errors | json}}
</div>

我們看到,如果不填電子郵件的話,錯誤的 JSON 是 {"required": true} ,這告訴我們目前有一個 required 的規則沒有被滿足。

驗證結果
驗證結果

當我們輸入一個字母 w 之后,就會發現錯誤變成了下面的樣子。這是因為我們對于 email 應用了多個規則,當必填項滿足后,系統會繼續檢查其他驗證結果。

{ 
"pattern": 
    { 
        "requiredPattern": "^([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|_|.]?)*[a-zA-Z0-9]+.[a-zA-Z]{2,4}$", 
        "actualValue": "w" 
    } 
}

通過幾次實驗,我們應該可以得出結論,當驗證未通過時,驗證器返回的是一個對象, key 為驗證的規則(比如 required, minlength 等),value 為驗證結果。如果驗證通過,返回的是一個 null

知道這一點后,我們其實就可以做出驗證出錯的提示了,為了方便引用,我們還是導出 ngModel 到一個 email 引用,然后就可以訪問這個 FormControl 的各個屬性了:驗證的狀態( valid/invalid )、控件的狀態(是否獲得過焦點 -- touched/untouched,是否更改過內容 -- pristine/dirty 等)

<label>
  <span>電子郵件地址</span>
  <input
    ...
    [ngModel]="user.email"
    #email="ngModel">
</label>
<div *ngIf="email.errors?.required && email.touched" class="error">
  email 是必填項
</div>
<div *ngIf="email.errors?.pattern && email.touched" class="error">
  email 格式不正確
</div>

自定義驗證

內建的驗證器對于兩個密碼比較的這種驗證是不夠的,那么這就需要我們自己定義一個驗證器。對于響應式表單來說,會比較簡單一些,但對于模版驅動的表單,這需要我們實現一個指令來使這個驗證器更通用和更一致。因為我們希望實現的樣子應該是和 requiredminlength 等差不多的形式,比如下面這個樣子 validateEqual="repeat"

<div>
    <label>
      <span>密碼</span>
      <input
        type="password"
        name="password"
        placeholder="請輸入您的密碼"
        [(ngModel)]="user.password"
        required
        minlength="8"
        validateEqual="repeat">
    </label>
    <label>
      <span>確認密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請再次輸入密碼"
        [(ngModel)]="user.repeat"
        required
        minlength="8">
    </label>
  </div>

那么要實現這種形式的驗證的話,我們需要建立一個指令,而且這個指令應該實現 Validator 接口。一個基礎的框架如下:

import { Directive, forwardRef } from '@angular/core';
import { NG_VALIDATORS, Validator, AbstractControl } from '@angular/forms';

@Directive({
  selector: '[validateEqual][ngModel]',
  providers: [
    { 
      provide: NG_VALIDATORS, 
      useExisting: forwardRef(()=>RepeatValidatorDirective), 
      multi: true 
    }
  ]
})
export class RepeatValidatorDirective implements Validator{
  constructor() { }
  validate(c: AbstractControl): { [key: string]: any } {
    return null;
  }
}

我們還沒有開始正式的寫驗證邏輯,但上面的框架已經出現了幾個有意思的點:

  1. Validator 接口要求必須實現的一個方法是 validate(c: AbstractControl): ValidationErrors | null; 。這個也就是我們前面提到的驗證正確返回 null 否則返回一個對象,雖然沒有嚴格的約束,但其 key 一般用于表示這個驗證器的名字或者驗證的規則名字,value 一般是失敗的原因或驗證結果。
  2. 和組件類似,指令也有 selector 這個元數據,用于選擇那個元素應用該指令,那么我們這里除了要求 DOM 元素應用 validateEqual 之外,還需要它是一個 ngModel 元素,這樣它才是一個 FormControl,我們在 validate 的時候才是合法的。
  3. 那么那個 providers 里面那些面目可憎的家伙又是干什么的呢? Angular 對于在一個 FormControl 上執行驗證器有一個內部機制: Angular 維護一個令牌為 NG_VALIDATORSmulti provider(簡單來說,Angular 為一個單一令牌注入多個值的這種形式叫 multi provider )。所有的內建驗證器都是加到這個 NG_VALIDATORS 的令牌上的,因此在做驗證時,Angular 是注入了 NG_VALIDATORS 的依賴,也就是所有的驗證器,然后一個個的按順序執行。因此我們這里也把自己加到這個 NG_VALIDATORS 中去。
  4. 但如果我們直接寫成 useExisting: RepeatValidatorDirective 會出現一個問題, RepeatValidatorDirective 還沒有生成,你怎么能在元數據中使用呢?這就需要使用 forwardRef 來解決這個問題,它接受一個返回一個類的函數作為參數,但這個函數不會立即被調用,而是在該類聲明后被調用,也就避免了 undefined 的狀況。

下面我們就來實現這個驗證邏輯,由于密碼和確認密碼有主從關系,并非完全的平行關系。也就是說,密碼是一個基準對比對象,當密碼改變時,我們不應該提示密碼和確認密碼不符,而是應該將錯誤放在確認密碼中。所以我們給出另一個屬性 reverse


export class RepeatValidatorDirective implements Validator{
  constructor(
    @Attribute('validateEqual') public validateEqual: string,
    @Attribute('reverse') public reverse: string) { }
  
  private get isReverse() {
    if (!this.reverse) return false;
    return this.reverse === 'true' ? true: false;
  }

  validate(c: AbstractControl): { [key: string]: any } {
    // 控件自身值
    let self = c.value;

    // 要對比的值,也就是在 validateEqual=“ctrlname” 的那個控件的值
    let target = c.root.get(this.validateEqual);

    // 不反向查詢且值不相等
    if (target && self !== target.value && !this.isReverse) {
      return {
        validateEqual: true
      }
    }

    // 反向查詢且值相等
    if (target && self === target.value && this.isReverse) {
        delete target.errors['validateEqual'];
        if (!Object.keys(target.errors).length) target.setErrors(null);
    }

    // 反向查詢且值不相等
    if (target && self !== target.value && this.isReverse) {
        target.setErrors({
            validateEqual: true
        })
    }

    return null;
  }
}

這樣改造后,我們的模版文件中對于密碼和確認密碼的驗證器如下:

<input
    type="password"
    name="password"
    placeholder="請輸入您的密碼"
    [(ngModel)]="user.password"
    #password="ngModel"
    required
    minlength="8"
    validateEqual="repeat"
    reverse="true">
<!-- 省略其他部分 -->
<input
    type="password"
    name="repeat"
    placeholder="請再次輸入密碼"
    [(ngModel)]="user.repeat"
    #repeat="ngModel"
    required
    minlength="8"
    validateEqual="password"
    reverse="false">
完成后的驗證錯誤提示
完成后的驗證錯誤提示

表單的提交

表單的提交比較簡單,綁定表單的 ngSubmit 事件即可

<form novalidate #f="ngForm" (ngSubmit)="onSubmit(f, $event)">

但需要注意的一點是,button如果不指定類型的話,會被當做 type="submit",所以當按鈕不是進行提交表單的話,需要顯式指定 type="button" 。而且如果遇到點擊提交按鈕頁面刷新的情況的話,意味著默認的表單提交事件引起了瀏覽器的刷新,這種時候需要阻止事件冒泡。

onSubmit({value, valid}, event: Event){ 
  if(valid){
    console.log(value);
  }
  event.preventDefault();
}

對于模板驅動的表單,我們就先總結到這里,下一篇文章我們會一起討論響應式表單。

本文代碼:https://github.com/wpcfan/ng-features.git

慕課網 Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

有問題的童鞋可以加入我的小密圈討論: http://t.xiaomiquan.com/jayRnaQ (該鏈接7天內(5月14日前)有效)

最后再提一下,我的 《Angular 從零到一》紙書出版了,歡迎大家圍觀、訂購、提出寶貴意見。

下面是書籍的內容簡介:

本書系統介紹Angular的基礎知識與開發技巧,可幫助前端開發者快速入門。共有9章,第1章介紹Angular的基本概念,第2~7章從零開始搭建一個待辦事項應用,然后逐步增加功能,如增加登錄驗證、將應用模塊化、多用戶版本的實現、使用第三方樣式庫、動態效果制作等。第8章介紹響應式編程的概念和Rx在Angular中的應用。第9章介紹在React中非常流行的Redux狀態管理機制,這種機制的引入可以讓代碼和邏輯隔離得更好,在團隊工作中強烈建議采用這種方案。本書不僅講解Angular的基本概念和最佳實踐,而且分享了作者解決問題的過程和邏輯,講解細膩,風趣幽默,適合有面向對象編程基礎的讀者閱讀。

慕課網 Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner

京東鏈接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,782評論 18 139
  • 細說 Angular 2+ 的表單(一):模板驅動型表單 響應式表單 響應式表單乍一看還是很像模板驅動型表單的,但...
    接灰的電子產品閱讀 3,388評論 5 22
  • 版本:Angular 5.0.0-alpha 表單是商業應用的支柱,我們用它來執行登錄、求助、下單、預訂機票、安排...
    soojade閱讀 1,285評論 0 1
  • 過濾器用來格式化需要展示給用戶的數據。AngularJS有很多實用的內置過濾器,同時也提供了方便的途徑可以自己創建...
    oWSQo閱讀 1,114評論 0 5
  • 主要內容 第一節 - 創建最簡單的輸入框 第二節 - 添加簡單的驗證功能 第三節 - 顯示驗證失敗的錯誤信息 第四...
    semlinker閱讀 1,240評論 0 4