1. 组件继承ControlValueAccessor,ControlValueAccessor接口需要实现三个必选方法
writeValue() 用于向元素中写入值,获取表单的元素的元素值
registerOnChange()设置一个当控件接受到改变的事件时所要调用的函数;这也是我们把变化 emit 回表单的机制;
registerOnTouched() 设置一个当控件接受到 touch 事件时所要调用的函数
export class ImageListSelectComponent implements ControlValueAccessor { _onChange = (_:any)=>{}; writeValue(obj: any): void{ this.selectedImg = obj; } registerOnChange(fn: (_: any) => void): void { this._onChange = fn; } registerOnTouched(fn: any): void { } }
2. 一个的 token 是 NG_VALUE_ACCESSOR 。这是将控件本身注册到 DI 框架成为一个可以让表单访问其值的控件。
但问题来了,如果在元数据中注册了控件本身,而此时控件仍未创建,这怎么破?这就得用到 forwardRef 了,这个函数允许我们引用一个尚未定义的对象。
另外一个 NG_VALIDATORS 是让控件注册成为一个可以让表单得到其验证状态的控件
providers:[
{
provide:NG_VALUE_ACCESSOR,
useExisting:forwardRef(()=>ImageListSelectComponent),
multi:true
},
{
provide:NG_VALIDATORS,
useExisting:forwardRef(()=>ImageListSelectComponent),
multi:true
}
]
3. 控件的验证器函数(必写方法,否则会报错)
validate(c:FormControl):{[key:string]:any}{
return this.selectedImg ? null :{
imageListNotValid:true
}
}
4. 表单控制器命名formControlName="avatar"
<app-image-list-select
[cols]="'6'"
[rowHeight]="'1:1'"
[items]='items'
[title]="'选择头像:'"
formControlName="avatar">
</app-image-list-select>
完整代码:
app-image-list-select.component.ts
1 import { Component, Input, forwardRef } from '@angular/core'; 2 import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl } from '@angular/forms'; 3 4 @Component({ 5 selector: 'app-image-list-select', 6 templateUrl: './image-list-select.component.html', 7 styles: [], 8 providers:[ 9 { 10 provide:NG_VALUE_ACCESSOR, 11 useExisting:forwardRef(()=>ImageListSelectComponent), 12 multi:true 13 }, 14 { 15 provide:NG_VALIDATORS, 16 useExisting:forwardRef(()=>ImageListSelectComponent), 17 multi:true 18 } 19 ] 20 }) 21 export class ImageListSelectComponent implements ControlValueAccessor { 22 23 @Input() cols=6; 24 @Input() rowHeight='1:1'; 25 @Input() items:string[]=[]; 26 @Input() title = '选择'; 27 selectedImg:string; 28 29 _onChange = (_:any)=>{}; 30 31 constructor() { } 32 33 writeValue(obj: any): void{ 34 console.log(obj); 35 this.selectedImg = obj; 36 } 37 registerOnChange(fn: (_: any) => void): void { 38 this._onChange = fn; 39 } 40 41 registerOnTouched(fn: any): void { 42 } 43 44 validate(c:FormControl):{[key:string]:any}{ 45 return this.selectedImg ? null :{ 46 imageListNotValid:true 47 } 48 } 49 50 changeSelected(index){ 51 this.selectedImg = this.items[index]; 52 this._onChange(this.selectedImg); 53 } 54 55 }
app-image-list-select.component.html
1 <div> 2 <h3>{{title}}</h3> 3 <mat-icon [svgIcon]='selectedImg'></mat-icon> 4 </div> 5 <mat-grid-list [cols]="cols" [rowHeight]="rowHeight"> 6 <mat-grid-tile *ngFor="let item of items;let i = index"> 7 <mat-icon [svgIcon]='item' (click)="changeSelected(i)"></mat-icon> 8 </mat-grid-tile> 9 </mat-grid-list>
register.component.html引用自定义表单控件app-image-list-select
1 <div class="login-wrap"> 2 <form [formGroup]="myGroup" (ngSubmit)="onSubmit(myGroup,$event)"> 3 <mat-card class="example-card"> 4 <mat-card-header><mat-card-title>注册</mat-card-title></mat-card-header> 5 <mat-card-content> 6 <mat-form-field> 7 <input matInput placeholder="您的email" formControlName="email"> 8 </mat-form-field> 9 <mat-form-field> 10 <input matInput placeholder="您的姓名" formControlName="username"> 11 </mat-form-field> 12 <mat-form-field> 13 <input matInput placeholder="您的密码" formControlName="password"> 14 </mat-form-field> 15 <mat-form-field> 16 <input matInput placeholder="请重新输入密码" formControlName="repassword"> 17 </mat-form-field> 18 <div> 19 <app-image-list-select 20 [cols]="'6'" 21 [rowHeight]="'1:1'" 22 [items]='items' 23 [title]="'选择头像:'" 24 formControlName="avatar"> 25 </app-image-list-select> 26 </div> 27 </mat-card-content> 28 <mat-card-actions> 29 <button mat-raised-button type="submit">注册</button> 30 </mat-card-actions> 31 </mat-card> 32 </form> 33 </div>
register.component.ts
1 import { Component, OnInit } from '@angular/core'; 2 import { FormGroup, FormBuilder } from '@angular/forms'; 3 4 @Component({ 5 selector: 'app-register', 6 templateUrl: './register.component.html', 7 styles: [` 8 mat-form-field{width:100%;} 9 form{ 10 width: 500px; 11 margin: 20px auto; 12 } 13 `] 14 }) 15 export class RegisterComponent implements OnInit { 16 17 myGroup:FormGroup; 18 items=[]; 19 constructor(private fb:FormBuilder) { } 20 21 ngOnInit() { 22 const nums = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]; 23 this.items = nums.map(d=> `avatars:svg-${d}`); 24 25 const img = `avatars:svg-${Math.floor(Math.random()*16).toFixed()}`; 26 27 this.myGroup = this.fb.group({ 28 email:[], 29 username:[], 30 password:[], 31 repassword:[], 32 avatar:[img] 33 }); 34 } 35 onSubmit({value,valid},ev:Event){ 36 console.log(value); 37 console.log(valid); 38 } 39 40 }