Angular響應式表單及封裝表單控件
響應式表單也叫模型驅動型表單。
有三個重要元素FormControl,FormGroup和FormBuilder。還有一個FormArray。
驗證器和異步驗證器。
動態指定驗證器。條件改變驗證方式改變。
自定義FormControl。用于表單過于復雜之后,邏輯難以理清楚。把復雜問題拆成若干簡單問題永遠是萬能鑰匙。用于簡化form表單自己的邏輯。
一、登錄表單

多個validators:
Validators.compose([Validators.required, Validators.email])返回ValidatorFn。
動態指定validator:
一開始可以不指定validator,在某些條件下動態指定validator:
this.form.controls['email'].setValidators(this.validate);
查看errors:
<mat-error>{{form.controls['email'].errors | json}}</mat-error>
模板:
<form [formGroup]="form" (ngSubmit)="onSubmit(form,$event)"> <mat-card class="example-card"> <mat-card-header> <mat-card-title>登錄:</mat-card-title> </mat-card-header> <mat-card-content> <mat-form-field class="example-full-width" class="full-width"> <input type="text" formControlName="email" matInput placeholder="您的email" style="text-align: right"> <mat-error>{{form.controls['email'].errors | json}}</mat-error> </mat-form-field> <mat-form-field class="example-full-width" class="full-width"> <input type="password" formControlName="password" matInput placeholder="您的密碼" style="text-align: right"> </mat-form-field> <button mat-raised-button type="submit" color="primary" [disabled]="!form.valid">登錄</button> </mat-card-content> <mat-card-actions class="text-right"> <p>還沒有賬戶?<a routerLink="/register">注冊</a></p> <p>忘記密碼?<a href="">找回</a></p> </mat-card-actions> </mat-card> <mat-card class="example-card"> <mat-card-header> <mat-card-title>每日佳句</mat-card-title> <mat-card-subtitle>滿足感在于不斷的努力,而不是現有成就。全心努力定會勝利滿滿。</mat-card-subtitle> </mat-card-header> <img mat-card-image src="/assets/images/quote_fallback.jpg" alt=""> <mat-card-content> Satisfaction lies in the effort, not in the attainment. Full effort is full victory. </mat-card-content> </mat-card> </form>
組件:
export class LoginComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) { // this.form = new FormGroup({ // email: new FormControl("wang@163.com", Validators.compose([Validators.required, Validators.email])), // password: new FormControl("",Validators.required), // }) //formBuilder不需要顯示的new FormControl this.form = this.fb.group({ email: ["wang@163.com", Validators.compose([Validators.required, Validators.email, this.validate]) ], password:["",Validators.required] }) } ngOnInit(): void { } onSubmit(form: FormGroup, event: Event) { event.preventDefault(); console.log(JSON.stringify(form.value)); console.log(form.valid); } validate(c:FormControl):{[key:string]:any} | null{ if(!c.value){ return null; } const pattern=/^wang+/; if(pattern.test(c.value)){ return null; }else{ return { emailNotValid: 'The email must start with wang' } } } }
二、封裝自定義表單控件
把注冊表單中的圖片列表抽成一個獨立組件。

ng g c shared/image-list-select生成組件
- 實現ControlValueAccessor接口。實現writeValue(),registerOnChange()和registerOnTouched()
- 定義一個providers,令牌NG_VALUE_ACCESSOR和NG_VALIDATORS。用useExisting加forwardRef。并且設置multi為true。
在image-list-select中可以放開的屬性有很多,有沒有必要一一放開?需要權衡。
如果想要充分的自由度的話,可以用transclude嵌入組件<ng-content></ng-content>。
在image-list-select中隔離封裝,放開有限的屬性。
模板:
<div> <span>{{title}}</span> <mat-icon class="avatar" [svgIcon]="selected" *ngIf="useSvgIcon else imgSelect"> </mat-icon> <ng-template #imgSelect> <img [src]="selected" alt="image selected" class="cover"> </ng-template> </div> <div class="scroll-container"> <mat-grid-list [cols]="cols" [rowHeight]="rowHight"> <mat-grid-tile *ngFor="let item of items; let i = index"> <div class="image-container" (click)="onChange(i)"> <mat-icon class="avatar" [svgIcon]="item" *ngIf="useSvgIcon else imgItem"></mat-icon> <ng-template #imgItem> <img [src]="item" alt="image item" [ngStyle]="{'width': itemWidth}"> </ng-template> <div class="after"> <div class="zoom"> <mat-icon>checked</mat-icon> </div> </div> </div> </mat-grid-tile> </mat-grid-list> </div>
組件:
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'app-image-list-select', templateUrl: './image-list-select.component.html', styleUrls: ['./image-list-select.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ImageListSelectComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => ImageListSelectComponent), multi: true } ] }) export class ImageListSelectComponent implements ControlValueAccessor { @Input() title = "選擇" @Input() cols = 6; @Input() rowHight = '64px' @Input() items: string[] = []; @Input() useSvgIcon: boolean = false; @Input() itemWidth = '80px'; selected: string = ''; constructor() { } private propagateChange = (_: any) => { }; //寫值,設置控件的值form中setValue設置初始值,通過表單控件的writeValue方法設值。 writeValue(obj: any): void { this.selected = obj; } //控件view發生變化,把變化emit給表單 registerOnChange(fn: any): void { this.propagateChange = fn; } //什么狀態算touched,告訴表單 registerOnTouched(fn: any): void { } onChange(i: number) { this.selected = this.items[i]; this.propagateChange(this.selected); //變化通知表單 } validate(c: FormControl): { [key: string]: any } | null { return this.selected ? null : { imageListInvalid: { valid: false } } } }
1,UI布局
圖片鼠標劃過去如果沒有任何反應,用戶會無法感知到有沒有選中,所以
<div class="image-container" (click)="onChange(i)"> <mat-icon class="avatar" [svgIcon]="avator"></mat-icon> <div class="after"> <div class="zoom"> <mat-icon>checked</mat-icon> </div> </div> </div>
讓組件既處理icon又處理圖片。用useSvgIcon控制,通過條件子句判斷。
<mat-icon class="" [svgIcon]="selected" *ngIf="useSvgIcon else imgSelect"> </mat-icon> <ng-template #imgSelect></ng-template>
list太多支持滾動。包在.scroll-container容器中。
.scroll-container { overflow-y: scroll; height: 200px; }
2,實現表單控件
可以通過 formContrlName來操作
實現ControlValueAccessor接口。
private propagateChange = (_: any) => { }; //寫值,設置控件的值form中setValue設置初始值,通過表單控件的writeValue方法設值。 writeValue(obj: any): void { this.selected = obj; } //控件view發生變化,把變化emit給表單 registerOnChange(fn: any): void { this.propagateChange = fn; } //什么狀態算touched,告訴表單 registerOnTouched(fn: any): void { throw new Error('Method not implemented.'); } onChange(i: number) { this.selected = this.items[i]; this.propagateChange(this.selected); //變化通知表單 }
providers中定義自己的provider,把自己注冊進去。
包括NG_VALUE_ACCESSOR和NG_VALIDATORS。
providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ImageListSelectComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => ImageListSelectComponent), multi: true } ] validate(c: FormControl): { [key: string]: any } | null { return this.selected ? null : { imageListInvalid: { valid: false } } }
3,調用/使用
在sharedModule中導出ImageListSelectComponent。
<app-image-list-select [useSvgIcon]="true" [cols]="6" [title]="'選擇頭像:'" [items]="items" formControlName="avatar"> </app-image-list-select>

2019-04-07
如果覺得本文對您有幫助~可以微信支持一下:




浙公網安備 33010602011771號