Angular 12 - ControlValueAccessor 和 patchValue

Angular 12 - ControlValueAccessor and patchValue

我正在努力解决影响我的自定义下拉组件的问题,该组件是使用 ControlValueAccessor 界面创建的。

基本上,这个下拉组件可以有两个不同的可能值:

export interface Key {
  group?: string;
  key?: string;
  order?: number;
  description?: string;
  defaultQ?: string;
}

总体而言,此自定义组件按如下方式正常工作:

当我尝试通过修补父组件的值来初始化此下拉组件时出现问题,如下所示:

this.formGroup.patchValue({ country: this.defaultCountry });

其中 this.defaultCountry 是一个 Key 对象,其描述为“意大利”。 事实证明,描述没有显示(而是显示 [object Object]),下拉组件后面的 CVA 值也没有更新(control.value 解析后的描述为空)。

似乎 patchValue 命令没有触发更新。

这是我当前的 DropdownComponent class:

@Component({
  selector: 'app-dropdown',
  templateUrl: './dropdown.component.html',
  styleUrls: ['./dropdown.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: DropdownComponent,
      multi: true
    }
  ]
})
export class DropdownComponent implements ControlValueAccessor, OnDestroy {
  private _destroyed: Subject<boolean> = new Subject();

  @Input()
  label = '';
  @Input()
  values: string[] | Key[] = [];
  @Input()
  readOnly = false;
  @Input()
  uppercase = false;
  @Input()
  filterResults = false;
  @Input()
  showErrors = true;
  @Input()
  position: DdPosition = 'bottom';
  @Input()
  keyGroup!: KeyGroup;
  @Input()
  formControl!: FormControl;
  @Input()
  formControlName!: string;

  @Output()
  selected: EventEmitter<unknown> = new EventEmitter<unknown>();
  @Output()
  valid: EventEmitter<boolean> = new EventEmitter<boolean>();

  onTouched = (): void => {};
  onChange = () => {};

  @ViewChild(FormControlDirective, { static: true })
  formControlDirective!: FormControlDirective;
  @ViewChild('valueSearch', { static: false })
  valueSearch: ElementRef<HTMLElement> | undefined;

  constructor(private controlContainer: ControlContainer, private keyService: KeyService) {
    this.keyService.keys$
      .pipe(
        takeUntil(this._destroyed),
        tap(keys => (this.values = keys.filter(k => k.group == this.keyGroup)))
      )
      .subscribe();
  }

  ngOnDestroy(): void {
    this._destroyed.next();
    this._destroyed.complete();
  }

  get control(): any {
    return this.formControl || this.controlContainer.control?.get(this.formControlName) || new FormControl();
  }

  get value(): any {
    if (!this.control.value) {
      return null;
    }
    if (this.keyGroup) {
      // Dropdown of keys
      return this.control.value[0] as Key;
    }
    return this.control.value;
  }

  getDescription(value: any): string { // this is the real value displayed by the HTML code
    if (!value) {
      return '';
    }
    if (typeof value === 'string') {
      return value;
    }
    // Dropdown of keys
    return (value as Key)?.description || '';
  }

  get stringsToFilter(): string[] {
    if (this.keyGroup) {
      // Dropdown of keys
      return (this.values as Key[]).map(k => k.description || '');
    }
    return this.values as string[];
  }

  clearInput(): void {
    if (this.control.disabled) {
      return;
    }
    this.control.setValue('');
    this.onChange();
    this.selected.emit(this.value);
    this.valueSearch?.nativeElement.blur();
  }

  onSelectChange(selected: string): void {
    if (this.control.disabled) {
      return;
    }
    if (this.keyGroup) {
      this.control.setValue((this.values as Key[]).filter(v => v.description === selected));
    } else {
      this.control.setValue(selected);
    }
    this.onInputChange();
  }

  onInputChange(): void {
    if (this.control.disabled) {
      return;
    }
    this.onChange();
    this.selected.emit(this.value);
  }

  onBlur(): void {
    this.onTouched();
  }

  registerOnTouched(fn: any): void {
    this.formControlDirective.valueAccessor?.registerOnTouched(fn);
  }

  registerOnChange(fn: any): void {
    this.formControlDirective.valueAccessor?.registerOnChange(fn);
  }

  writeValue(obj: any): void {
    this.formControlDirective.valueAccessor?.writeValue(this.getDescription(obj));
  }

  setDisabledState(isDisabled: boolean): void {
    this.formControlDirective.valueAccessor?.setDisabledState?.(isDisabled);
  }

  get isValueInList(): boolean {
    if (!this.getDescription(this.value) || this.getDescription(this.value) == '') {
      return true;
    }
    return this.values
      .map(v => (this.keyGroup ? (v as Key).description : (v as string)))
      .includes(this.getDescription(this.value));
  }

  get invalid(): boolean {
    return (this.control ? this.control.invalid : false) || !this.isValueInList;
  }

  get hasErrors(): boolean {
    if (!this.control) {
      return false;
    }
    const { dirty, touched } = this.control;
    return this.invalid ? dirty || touched : false;
  }
}

这是 DropdownComponent 的 HTML 代码:

<div class="text-xs dropdown">
  
[...]

      <!-- Selected value -->
      <input
        name="select"
        id="select"
        class="px-4 appearance-none outline-none text-gray-800 w-full"
        autocomplete="off"
        [ngClass]="{
          'uppercase': uppercase,
          'cursor-pointer': readOnly
        }"
        [value]="getDescription(value)"
        [formControl]="control"
        [readOnly]="readOnly"
        (blur)="onBlur()"
        (change)="onInputChange()"
        #valueSearch
      />
      
[...]

</div>

我在这里错过了什么? 你能帮帮我吗?

谢谢。

此致, A.M.

我设法通过在我的 DropdownComponent class 中注入 ChangeDetectorRef 并按如下方式使用它来解决我的问题:

writeValue(obj: any): void {
    this.formControlDirective.valueAccessor?.writeValue(obj);
    this.cdr.detectChanges();
  }

像这样,值被正确更新并显示在页面中。