custom form control 之前就写过了,这里简单写一下.
创建一个组件实现 ControlValueAccessor 接口
@Component({ providers: [ { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: MyInputComponent }, ], }) export class MyInputComponent implements ControlValueAccessor {}
实现 writeValue, model -> view 的时候被调用的,这里实现如何更新 view. 如果时 OnPush 记得要 markForCheck
writeValue(value: any): void { console.log('writeValue'); this.cdr.markForCheck(); this.value = value; }
实现 registerOnChange 方法. view -> model 内部通过调用这个方法向外输出值
private onChangeFn: (value: any) => void; registerOnChange(fn: MyInputComponent['onChangeFn']): void { this.onChangeFn = fn; }
实现 registerOnTouched 方法, view -> model, 当内部 touched 了通知外部
private onTouchedFn: () => void; registerOnTouched(fn: MyInputComponent['onTouchedFn']): void { console.log('registerOnTouched'); this.onTouchedFn = fn; }
实现 setDisabledState 方法, model -> view, 当外部设定 disabled 后, 内部更新 view
setDisabledState(isDisabled: boolean): void { console.log('setDisabledState'); this.cdr.markForCheck(); this.disabled = isDisabled; }
执行的顺序是 ngOnInit-> DoCheck -> writeValue-> registerOnChange -> registerOnTouched -> setDisabledState -> ngAfterContentInit ...
实现好了 customer accessor 现在我们来把它放进 mat form filed 里
refer https://material.angular.io/guide/creating-a-custom-form-field-control
要做到这一点, 组件必须实现 MatFormFieldControl 里面有 14 个接口要实现的.
@Directive() export abstract class MatFormFieldControl<T> { value: T | null; readonly stateChanges: Observable<void>; readonly id: string; readonly placeholder: string; readonly ngControl: NgControl | null; readonly focused: boolean; readonly empty: boolean; readonly shouldLabelFloat: boolean; readonly required: boolean; readonly disabled: boolean; readonly errorState: boolean; readonly controlType?: string; readonly autofilled?: boolean; abstract setDescribedByIds(ids: string[]): void; abstract onContainerClick(event: MouseEvent): void; }
还需要提供 provider, 这里要注意 : 由于我们的组件本身就是 value accessor 所以我们需要动一点手脚, 删除 provide NG_VALUE_ACCESSOR 改用 ngControl.valueAccessor = this 的方式去做.
@Component({ providers: [ { provide: MatFormFieldControl, useExisting: MyInputComponent }, // { provide: NG_VALUE_ACCESSOR, multi: true, useExisting: MyInputComponent }, ], }) export class MyInputComponent implements MatFormFieldControl<Value> { constructor( @Optional() @Self() public ngControl: NgControl, ) { if (ngControl != null) { ngControl.valueAccessor = this; } } }
其它部分都很好理解.
stateChanges 是用来通知 form field 要 mark for check 的, 当我们内部修改后,要通知它就用这个.
要记得释放
ngOnDestroy() { this.stateChanges.complete(); this.focusMonitor.stopMonitoring(this.hostElement.nativeElement); }
其它的都是一些属性. 一般上会使用 getter setter 去维护更新,比如
public get focused(): boolean { return this._focused; } public set focused(v: boolean) { this._focused = v; this.stateChanges.next(); } private _focused: boolean;
比如
get empty() { return this.value === ''; } get shouldLabelFloat() { return this.focused || !this.empty; }
关于 focus 通常使用 monitor 来监听
focusMonitor.monitor(hostElement.nativeElement, true).subscribe(origin => { this.focused = origin === null ? false : true; });
onContainerClick(event: MouseEvent) { // if ((event.target as Element).tagName.toLowerCase() != 'input') { // this.elRef.nativeElement.querySelector('input').focus(); // } }
<input matInput placeholder="Email" [formControl]="emailFormControl"
[errorStateMatcher]="matcher">