https://www.youtube.com/watch?v=t7MEvi9RdNI&list=PL9LUW6O9WZqgUMHwDsKQf3prtqVvjGZ6S&index=6
Template-Driven Form 內容簡單,適合新手入門
簡單
,沒有複雜的驗證規則,例如:連動的欄位驗證不需要
自定義validator(自定義validator很麻煩)不需要
動態表單非同步
行為,在取得表單實體
是有限制的,需注意生命週期@Input xxx:xxxType
ngOnChanges(changes: SimpleChanges) { ... }
表單實體
一開始就存在本集開始沒看文件,Kevin 老師直接寫程式
首先,先開好片頭導讀的文章,再跟著影片一起看,以後才有能力自己查文件
今天內容有:(方便search)
<select>
選單<select>
CompareWith input在app.module.ts要imports:[FormsModule]
但Template-Driven Form有其起手式,我們來看最簡單的形式
<form #f="ngForm"> 為了取值,透過templateRef吐出ngForm(最上層的FormGroup)
^^^^^^^^^^ 非必加
<input name="firstName" ngModel /> name跟ngModel必加
^^^^ ^^^^^^^ 必加,否則立刻報錯
</form>
{{ f.value | json }} 會輸出整個form的東西
請參考:NgForm (DIRECTIVE)的value屬性
NgForm、NgModel、FormControl 之間的關係,跟各自有哪些屬性、方法可用?
就我的理解
NgForm是最上層的FormGroup instance
NgModel 會從 domain model 去建立一個 FormControl
API > @angular/forms
https://angular.io/api/forms/NgForm
建一個最上層的FormGroup instance,並"綁定"form後就能:
FormGroup // 繼承 forms/AbstractControl
https://angular.io/api/forms/FormGroup
API > @angular/forms
https://angular.io/api/forms/FormControl
Tracks the value and validation status of an individual form control.
無論在Reactive Form或Template-Driven Form,都用FormControl
form裡面的input element就代表一個FormControl
<form #f="ngForm">
<input name="firstName" ngModel /> input element這就是FormControl
</form>
FormControl包含什麼?以input element為例,包含
class FormControl extends AbstractControl {
constructor(formState: any = null, validatorOrOpts?: ValidatorFn | AbstractControlOptions | ValidatorFn[], asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[])
...
// inherited from forms/AbstractControl 繼承forms/AbstractControl
constructor(validator: ValidatorFn, asyncValidator: AsyncValidatorFn)
// Template-Driven Form 主要是對 屬性 的熟悉度,例如:
// 該contorl目前的值,有4類(簡單的值、key-value pair、object、array),可先console.log出來
value: any
// 指定一個 ValidatorFn 來驗證control的值是否合法
validator: ValidatorFn | null
// 狀態
status: string // VALID | INVALID | PENDING(該control正在檢查) | DISABLED(此control不受檢查)
// 值有沒有被改過
dirty: boolean
// method(方法),在Template-Driven Form,能用的method較少,主要是 屬性、狀態 用較多
// 設定表單驗證
setErrors(errors: ValidationErrors, opts: { emitEvent?: boolean; } = {}): void
<form #f="ngForm">
<input name="firstName" ngModel #n="ngModel"/> input element這就是FormControl
</form>
{{ n.value | json }}
{{ n.valid }}
請參考FormControl有哪些 屬性 可用
https://angular.io/api/forms/FormControl
Create an initial HTML form template
https://angular.io/guide/forms#create-an-initial-html-form-template
在Template Driven Forms中,只要import FormsModule,
即使是HTML5的一般的form表單,也可適用FormsModule
import { FormsModule } from '@angular/forms';
@NgModule({
imports: [
FormsModule // import FormsModule
],
})
export class AppModule { }
不用對<form>
執行任何操作即可使用FormsModule
範例中的CSS跟FormsModule無關,class="form-group"單純只是bootstrap的樣式
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
<form>
class裡的form-group與angular的FormGroup無關喔!!
<div class="form-group"> // class不是必要的,只是bootstrap的表單樣式
<label for="name">Name</label>
<input type="text" class="form-control" id="name" required>
</div>
<button type="submit" class="btn btn-success">Submit</button>
</form>
<select>
選單遇到選單用*ngFor
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" id="power" required>
<option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
control的value可以直接帶進去
</select>
</div>
請參考:NgSelectOption (Directive) 裡的 @Input()ngValue:any
https://angular.io/api/forms/NgSelectOption
NgSelectOption 的 屬性:
<select>
CompareWith input在NgSelectOption DIRECTIVE按右邊的<>
https://angular.io/api/forms/NgSelectOption
就會進到程式碼
https://github.com/angular/angular/blob/8.2.5/packages/forms/src/directives/select_control_value_accessor.ts#L191-L256
再搜尋compareWith
const selectedCountriesControl = new FormControl();
當selectOption使用ngValue時,通常在<select>
加[compareWith]
指定一個function()
當傳入的值 ngValue 是 Object 的時候,若要設定預設值,要加 [compareWith]="compareFn"
<select [compareWith]="compareFn" [formControl]="selectedCountriesControl">
<option *ngFor="let country of countries" [ngValue]="country">
{{country.name}}
</option>
</select>
compareFn(c1: Country, c2: Country): boolean {
// 用id來比對
return c1 && c2 ? c1.id === c2.id : c1 === c2;
}
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name">
^^^^^^^^^^^^^^^^^^^^^^^
TODO: remove this: {{model.name}}
共有3種狀態:
<form ...>
<input _ngcontent-c68 name="firstName" ngmodel required ng-reflect-required ng-reflect-name="firstName" ng-reflect-model
class="ng-pristine ng-invalid ng-touched">
^^^^^^^^^ ^^^^^^^^ ^^^^^^^^ 依input狀態,Angular自動幫你替換
</form>
範例:
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
加在index.html 適用全專案的forms
<link rel="stylesheet" href="assets/forms.css">
<input type="text" class="form-control" id="name"
required
[(ngModel)]="model.name" name="name"
#spy>
<br>TODO: remove this: {{spy.className}}
<form #f="ngForm">
^^ ngForm export出來的樣板變數
<label for="name">Name</label>
<input type="text" class="form-control" id="name"
required minlength="3"
^^^^^^^ ^^^^^^^^^ 直接在input加上驗證條件即可
Template-Driven的自定義validator應該較麻煩,老師沒演示
[(ngModel)]="model.name" name="name"
#name="ngModel">
^^^^^template reference
</form>
{{ f.value | json }} 觀察form的value
<div [hidden]="name.valid || name.pristine"
^^^^利用template reference取到
class="alert alert-danger">
Name is required
</div>
用hasError('error的類型') 可知發生什麼錯誤 => 可自定義錯誤訊息
<span *ngIf="name.hasError('required')"> 當有name有required的error時
欄位必填 ^^^^^^^^^ 此input control有無required錯誤訊息?
(可自定義錯誤訊息)
</span>
{{ name.valid }} 當control驗證失敗時 => false
{{ name.errors | json }} 當control的errors就會包含 => {"required":true }
<form #f="ngForm" (ngSubmit)="save(f)">
^^^^^^ 方法二:直接用(ngSubmit)
^^^^^^ 只能在Template Driven使用,無法用於Reactive Form
<input name="firstName" ngModel #n="ngModel" />
方法一:<button type="submit" (click)="save(f)">
^^^當type為submit時 ^^ 把form group的內容傳到ts裡
</form>
save(f){
console.log(f); // 觀察
// controls 有哪些control
// NgForm.form 這才是FormsGroup
// .getRawValue 取得所有value,包含disabled的input element
// NgForm.EventEmitter
// NgForm.value:Object
// firstName: "123"
//
}
<form #f="ngForm">
<input name="firstName" ngModel #n1="ngModel" />
^^ template reference不能重複
<input name="lastName" ngModel #n2="ngModel" disabled/>
^^ ^^^^^^^^ 若disabled印不出value
</form>
{{ f.value | json }} 只會顯示 { "firstName":"" }
{{ f.form.getRawValue() | json } 用form.getRawValue()取得所有value
@Directive({ // 其實是Directive
selector: '[appForbiddenName]',
providers: [{provide: NG_VALIDATORS, useExisting: ForbiddenValidatorDirective, multi: true}]
^^^^^^必須註冊在NG_VALIDATORS multi:true一定要加 ^^^^
})
export class ForbiddenValidatorDirective implements Validator {
@Input('appForbiddenName') forbiddenName: string;
validate(control: AbstractControl): {[key: string]: any} | null {
^^^^^^^^ 實作驗證用的function() ^^^^^^^^^^^^^^^^^^^^^^^^^^回傳Object或null
return this.forbiddenName ? forbiddenNameValidator(new RegExp(this.forbiddenName, 'i'))(control) : null;
}
}