Angular 学习笔记 (Custom Accessor + Mat FormField + Custom select)

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.target 确认用户点击的是内部组件或者真的是外部的 container 做相应的处理
onContainerClick(event: MouseEvent) {
  // if ((event.target as Element).tagName.toLowerCase() != 'input') {
  //   this.elRef.nativeElement.querySelector('input').focus();
  // }
}
errorState 是一个比较烦人的东西
这个是用在做什么时候需要显示 error 的情况.form field 本身是不处理这个的,是交由 accessor 管理的. 
material 有一个叫 errorStateMatcher 的 class 我们的组件最好也可以支持这样的设定,这样就比较统一. 
下面是 input 的做法.
   <input matInput placeholder="Email" [formControl]="emailFormControl"
           [errorStateMatcher]="matcher">
 
 
原文地址:https://www.cnblogs.com/keatkeat/p/11941939.html