細(xì)說 Angular 2+ 的表單(一):模板驅(qū)動(dòng)型表單

細(xì)說 Angular 2+ 的表單(二):響應(yīng)式表單

摘要

在企業(yè)應(yīng)用開發(fā)時(shí),表單是一個(gè)躲不過去的事情,和面向消費(fèi)者的應(yīng)用不同,企業(yè)領(lǐng)域的開發(fā)中,表單的使用量是驚人的。這些表單的處理其實(shí)是一個(gè)挺復(fù)雜的事情,比如有的是涉及到多個(gè) Tab 的表單,有的是向?qū)问蕉鄠€(gè)步驟的,各種復(fù)雜的驗(yàn)證邏輯和時(shí)不時(shí)需要彈出的對話框等等。筆者試圖在這一系列文章中對 Angular 中的表單處理做一個(gè)相對完整的梳理。

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

第一篇主要介紹模版驅(qū)動(dòng)型的表單。

模版驅(qū)動(dòng)的表單

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

模版驅(qū)動(dòng)的例子

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

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

export interface Address {
  province: string; // 省份
  city: string; // 城市
  area: string; // 區(qū)縣
  addr: string; // 詳細(xì)地址
}

接下來我們建立模版文件,一個(gè)最簡單的 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>確認(rèn)密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請?jiān)俅屋斎朊艽a">
    </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>區(qū)縣</span>
      <select name="area">
        <option value="">請選擇區(qū)縣</option>
      </select>
    </label>
    <label>
      <span>地址</span>
      <input type="text" name="addr">
    </label>
  </div>
  <button type="submit">注冊</button>
</form>

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

簡單的Form
簡單的Form

數(shù)據(jù)綁定

對于模版驅(qū)動(dòng)型的表單處理,我們首先需要在對應(yīng)的模塊中引入 FormsModule ,這一點(diǎn)千萬不要忘記了。

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 { }

進(jìn)行模版驅(qū)動(dòng)類型的表單處理的一個(gè)必要步驟就是建立數(shù)據(jù)的雙向綁定,那么我們需要在組件中建立一個(gè)類型為 User 的成員變量并賦初始值。

// template-driven.component.ts
// 省略元數(shù)據(jù)和導(dǎo)入的類庫信息
export class TemplateDrivenComponent implements OnInit {

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

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

令人困惑的 ngModel

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

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 ,沒有任何中括號(hào)小括號(hào)的話,這代表著我們創(chuàng)建了一個(gè) FormControl 的實(shí)例,這個(gè)實(shí)例將會(huì)跟蹤值的變化、用戶的交互、驗(yàn)證狀態(tài)以及保持視圖和領(lǐng)域?qū)ο蟮耐降裙ぷ鳌?/p>

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

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

其實(shí)在我們導(dǎo)入 FormsModule 的時(shí)候,所有的 <form> 標(biāo)簽都會(huì)默認(rèn)的被認(rèn)為是一個(gè) NgForm ,因此我們并不需要顯式的在標(biāo)簽中寫 ngForm 這個(gè)指令。

<!-- ngForm 并不需要顯示聲明,任何 <form> 標(biāo)簽?zāi)J(rèn)都是 ngForm -->
<form novalidate ngForm>
  <input
    type="text"
    name="email"
    placeholder="請輸入您的 email 地址"
    ngModel>
</form>

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

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

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

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

單向數(shù)據(jù)綁定

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

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

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

單向數(shù)據(jù)綁定
單向數(shù)據(jù)綁定

雙向數(shù)據(jù)綁定

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

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

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

輸入的值影響了表單,但不會(huì)影響領(lǐng)域?qū)ο?><div   id=輸入的值影響了表單,但不會(huì)影響領(lǐng)域?qū)ο?/div>

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

表單和領(lǐng)域?qū)ο蟮闹当3至送?><div   id=表單和領(lǐng)域?qū)ο蟮闹当3至送?/div>

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

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

ngModelGroup 是什么鬼?

如果我們仔細(xì)觀察上面的輸出的話,會(huì)發(fā)現(xiàn)一個(gè)問題: user 中是有一個(gè)嵌套對象 address 的,而表單中沒有嵌套對象的。如果要實(shí)現(xiàn)表單中的結(jié)構(gòu)和領(lǐng)域?qū)ο蟮慕Y(jié)構(gòu)一致的話,我們就得請出 ngModelGroup 了。ngModelGroup 會(huì)創(chuàng)建并綁定一個(gè) FormGroup 到該 DOM 元素。 FormGroup 又是什么呢?簡單來說,是一組 FormControl。

  <!-- 使用 ngModelGroup 來創(chuàng)建并綁定 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>

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

表單和領(lǐng)域?qū)ο蟮慕Y(jié)構(gòu)也完全一致了
表單和領(lǐng)域?qū)ο蟮慕Y(jié)構(gòu)也完全一致了

數(shù)據(jù)驗(yàn)證

模版驅(qū)動(dòng)型的表單的驗(yàn)證也是主要由模版來處理的,在看怎么使用之前,需要界定一下驗(yàn)證規(guī)則:

  • 三個(gè)必填項(xiàng): email, passwordrepeat
  • email 的形式需要符合電子郵件的標(biāo)準(zhǔn)
  • passwordrepeat 必須一致

當(dāng)然除了這幾個(gè)規(guī)則,我們還希望在表單未驗(yàn)證通過時(shí)提交按鈕是不可用的。

<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>確認(rèn)密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請?jiān)俅屋斎朊艽a"
        [(ngModel)]="user.repeat"
        required
        minlength="8">
    </label>
  </div>
  <!-- 省略其他部分 -->
  <button type="submit" [disabled]="f.invalid">注冊</button>
</form>
<div>

Angular 中有幾種內(nèi)建支持的驗(yàn)證器( Validators )

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

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

<div>
  <span>email 驗(yàn)證:</span> {{f.controls.email?.errors | json}}
</div>

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

驗(yàn)證結(jié)果
驗(yàn)證結(jié)果

當(dāng)我們輸入一個(gè)字母 w 之后,就會(huì)發(fā)現(xiàn)錯(cuò)誤變成了下面的樣子。這是因?yàn)槲覀儗τ?email 應(yīng)用了多個(gè)規(guī)則,當(dāng)必填項(xiàng)滿足后,系統(tǒng)會(huì)繼續(xù)檢查其他驗(yàn)證結(jié)果。

{ 
"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" 
    } 
}

通過幾次實(shí)驗(yàn),我們應(yīng)該可以得出結(jié)論,當(dāng)驗(yàn)證未通過時(shí),驗(yàn)證器返回的是一個(gè)對象, key 為驗(yàn)證的規(guī)則(比如 required, minlength 等),value 為驗(yàn)證結(jié)果。如果驗(yàn)證通過,返回的是一個(gè) null

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

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

自定義驗(yàn)證

內(nèi)建的驗(yàn)證器對于兩個(gè)密碼比較的這種驗(yàn)證是不夠的,那么這就需要我們自己定義一個(gè)驗(yàn)證器。對于響應(yīng)式表單來說,會(huì)比較簡單一些,但對于模版驅(qū)動(dòng)的表單,這需要我們實(shí)現(xiàn)一個(gè)指令來使這個(gè)驗(yàn)證器更通用和更一致。因?yàn)槲覀兿M麑?shí)現(xiàn)的樣子應(yīng)該是和 requiredminlength 等差不多的形式,比如下面這個(gè)樣子 validateEqual="repeat"

<div>
    <label>
      <span>密碼</span>
      <input
        type="password"
        name="password"
        placeholder="請輸入您的密碼"
        [(ngModel)]="user.password"
        required
        minlength="8"
        validateEqual="repeat">
    </label>
    <label>
      <span>確認(rèn)密碼</span>
      <input
        type="password"
        name="repeat"
        placeholder="請?jiān)俅屋斎朊艽a"
        [(ngModel)]="user.repeat"
        required
        minlength="8">
    </label>
  </div>

那么要實(shí)現(xiàn)這種形式的驗(yàn)證的話,我們需要建立一個(gè)指令,而且這個(gè)指令應(yīng)該實(shí)現(xiàn) Validator 接口。一個(gè)基礎(chǔ)的框架如下:

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;
  }
}

我們還沒有開始正式的寫驗(yàn)證邏輯,但上面的框架已經(jīng)出現(xiàn)了幾個(gè)有意思的點(diǎn):

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

下面我們就來實(shí)現(xiàn)這個(gè)驗(yàn)證邏輯,由于密碼和確認(rèn)密碼有主從關(guān)系,并非完全的平行關(guān)系。也就是說,密碼是一個(gè)基準(zhǔn)對比對象,當(dāng)密碼改變時(shí),我們不應(yīng)該提示密碼和確認(rèn)密碼不符,而是應(yīng)該將錯(cuò)誤放在確認(rèn)密碼中。所以我們給出另一個(gè)屬性 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” 的那個(gè)控件的值
    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;
  }
}

這樣改造后,我們的模版文件中對于密碼和確認(rèn)密碼的驗(yàn)證器如下:

<input
    type="password"
    name="password"
    placeholder="請輸入您的密碼"
    [(ngModel)]="user.password"
    #password="ngModel"
    required
    minlength="8"
    validateEqual="repeat"
    reverse="true">
<!-- 省略其他部分 -->
<input
    type="password"
    name="repeat"
    placeholder="請?jiān)俅屋斎朊艽a"
    [(ngModel)]="user.repeat"
    #repeat="ngModel"
    required
    minlength="8"
    validateEqual="password"
    reverse="false">
完成后的驗(yàn)證錯(cuò)誤提示
完成后的驗(yàn)證錯(cuò)誤提示

表單的提交

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

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

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

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

對于模板驅(qū)動(dòng)的表單,我們就先總結(jié)到這里,下一篇文章我們會(huì)一起討論響應(yīng)式表單。

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

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

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

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

下面是書籍的內(nèi)容簡介:

本書系統(tǒng)介紹Angular的基礎(chǔ)知識(shí)與開發(fā)技巧,可幫助前端開發(fā)者快速入門。共有9章,第1章介紹Angular的基本概念,第2~7章從零開始搭建一個(gè)待辦事項(xiàng)應(yīng)用,然后逐步增加功能,如增加登錄驗(yàn)證、將應(yīng)用模塊化、多用戶版本的實(shí)現(xiàn)、使用第三方樣式庫、動(dòng)態(tài)效果制作等。第8章介紹響應(yīng)式編程的概念和Rx在Angular中的應(yīng)用。第9章介紹在React中非常流行的Redux狀態(tài)管理機(jī)制,這種機(jī)制的引入可以讓代碼和邏輯隔離得更好,在團(tuán)隊(duì)工作中強(qiáng)烈建議采用這種方案。本書不僅講解Angular的基本概念和最佳實(shí)踐,而且分享了作者解決問題的過程和邏輯,講解細(xì)膩,風(fēng)趣幽默,適合有面向?qū)ο缶幊袒A(chǔ)的讀者閱讀。

慕課網(wǎng) 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

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