Angular Material 自定义表单字段控件的值未在调用 FormGroup 中更新

Angular Material custom form field control's value is not updated in the calling FormGroup

我按照 this 指南在 Angular Material 中制作了自定义表单字段控件。

然后我在 FormGroup 中添加这个控件。但我这里的问题是 FormGroup 无法获得自定义控件的正确值。它总是得到 undefined。我确实检查了是否将正确的值从输入播种到自定义控件中的 value 属性,它确实如此。

这可能是什么问题?

我的自定义控件: 组件

import { Component, OnDestroy, HostBinding, Input, Optional, Self, ElementRef } from '@angular/core';
import { MatFormFieldControl } from '@angular/material/form-field';
import { Subject } from 'rxjs/internal/Subject';
import { NgControl, ControlValueAccessor, FormBuilder, FormGroup } from '@angular/forms';
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';

@Component({
  selector: 'app-test-input',
  templateUrl: './test-input.html',
  styleUrls: ['./test-input.scss'],
  providers: [{ provide: MatFormFieldControl, useExisting: MyTestInput }]
})
export class MyTestInput implements MatFormFieldControl<string>, OnDestroy, ControlValueAccessor {
  static nextId = 0;
  FormGrp: FormGroup;
  stateChanges = new Subject<void>();
  private val: string;
  private ph: string;
  private req = false;
  private dis = false;
  onChange: () => void;
  onTouched: () => void;

  public get value(): string {
    return this.val;
  }

  public set value(val: string) {
    this.val = val;
    this.stateChanges.next();
  }

  controlType = 'my-test-input';

  @HostBinding() id = `${this.controlType}-${MyTestInput.nextId++}`;

  @Input()
  get placeholder() {
    return this.ph;
  }
  set placeholder(plh) {
    this.ph = plh;
    this.stateChanges.next();
  }

  focused = false;

  get empty() {
    return false;
  }

  @HostBinding('class.floating')
  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }

  @Input()
  get required() {
    return this.req;
  }
  set required(req) {
    this.req = coerceBooleanProperty(req);
    this.stateChanges.next();
  }

  @Input()
  get disabled(): boolean { return this.dis; }
  set disabled(value: boolean) {
    this.dis = coerceBooleanProperty(value);
    this.stateChanges.next();
  }

  errorState = this.FormGrp == null ? false : this.FormGrp.invalid;

  @HostBinding('attr.aria-describedby') describedBy = '';

  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  onContainerClick(event: MouseEvent) {
    if ((event.target as Element).tagName.toLowerCase() !== 'input') {
      this.elRef.nativeElement.querySelector('input').focus();
    }
    this.onTouched();
  }

  ngOnDestroy() {
    this.stateChanges.complete();
    this.fm.stopMonitoring(this.elRef.nativeElement);
  }


  constructor(
    @Optional() @Self() public ngControl: NgControl,
    fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) {
    this.FormGrp = fb.group({
      data: ['', this.required]
    });
    if (ngControl != null) {
      ngControl.valueAccessor = this;
    }
    fm.monitor(elRef.nativeElement, true).subscribe(origin => {
      this.focused = !!origin;
      this.stateChanges.next();
    });
  }

  writeValue(value: any): void {
    this.FormGrp.get('data').setValue(value);
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  setDisabledState(isDisabled: boolean): void {
    isDisabled ? this.FormGrp.get('data').disable() : this.FormGrp.get('data').enable();
  }

  input() {
    this.value = this.FormGrp.get('data').value;
    this.onChange();
  }
}

模板:

<div [formGroup]="FormGrp">
    <input formControlName="data" (input)="input()">
</div>

我的调用形式:

<form [formGroup]="Form">  
    <mat-form-field>
        <app-test-input formControlName="testControl"></app-test-input>
    </mat-form-field>
    <button>Submit</button>
</form>
<p *ngIf="Form">
    {{Form.value | json}}
</p>

我的调用表单定义:

this.Form = fb.group({
  testControl: ['', [Validators.required]]
});

您忘记将更新后的值传递给 onChange 方法,该方法是 ControlValueAccessor 实现的一部分:

测试-input.component.ts

export class TestInputComponent ... {
 onChange = (_: any) => {};

 ...
 input() {
    this.value = this.FormGrp.get('data').value;

    this.onChange(this.value);
                      \/
                  pass newValue
  }

Stackblitz Example