0

我一直在关注 angular-material 文档,该文档着眼于创建自定义表单字段控件: https ://material.angular.io/guide/creating-a-custom-form-field-control

它可以方便地跳过模板和反应形式的完整示例,因此我一直在争先恐后地尝试将其全部连接起来。

我已经尝试过了,取得了不同程度的成功。尽管还有其他问题,但我首先想了解如何让这个自定义字段识别它的时间invalid,以便我可以执行<mat-error>您在下面看到的操作(我删除了*ngIf只是这样我可以看到 的状态invalid)。{{symbolInput.invalid}}总是,而false实际上它应该true是所需的字段!

自定义 MatFormFieldControl 模板使用:

<mat-form-field class="symbol">
    <symbol-input
      name="symbol"
      placeholder="Symbol"
      ngModel
      #symbolInput="ngModel"
      [(ngModel)]="symbol"
      required></symbol-input>
    <button
      mat-button matSuffix mat-icon-button
      *ngIf="symbol && (symbol.asset1 || symbol.asset2)"
      aria-label="Clear"
      (click)="clearSymbol()">
      <mat-icon>close</mat-icon>
    </button>
    <mat-error >{{symbolInput.invalid}}</mat-error>
  </mat-form-field>

自定义 MatFormFieldControl 类:

export interface AssetSymbol {
  asset1: string, asset2: string
}

@Component({
  selector: 'symbol-input',
  templateUrl: './symbol-input.component.html',
  styleUrls: ['./symbol-input.component.css'],
  providers: [{ provide: MatFormFieldControl, useExisting: SymbolInputComponent}]
})
export class SymbolInputComponent implements MatFormFieldControl<AssetSymbol>, OnDestroy {
  static nextId = 0;

  stateChanges = new Subject<void>();
  parts: FormGroup;
  focused = false;
  errorState = false;
  controlType = 'symbol-input';
  onChangeCallback;

  @HostBinding() id = `symbol-input-${SymbolInputComponent.nextId++}`;
  @HostBinding('class.floating')
  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }
  @HostBinding('attr.aria-describedby')
  describedBy = '';
  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  get empty() {
    let n = this.parts.value;
    return !n.asset1 && !n.asset2;
  }

  @Input()
  get value(): AssetSymbol | null {
    let n = this.parts.value;
    return { asset1: n.asset1, asset2: n.asset2};
  }
  set value(symbol: AssetSymbol | null) {
    symbol = symbol || { asset1: "", asset2: ""};
    this.parts.setValue({asset1: symbol.asset1, asset2: symbol.asset2});
    this.stateChanges.next();
  }
  @Input()
  get placeholder() {
    return this._placeholder;
  }
  set placeholder(plh) {
    this._placeholder = plh;
    this.stateChanges.next();
  }
  private _placeholder: string;
  @Input()
  get required() {
    return this._required;
  }
  set required(req) {
    this._required = coerceBooleanProperty(req);
    this.stateChanges.next();
  }
  private _required = false;
  @Input()
  get disabled() {
    return this._disabled;
  }
  set disabled(dis) {
    this._disabled = coerceBooleanProperty(dis);
    this.stateChanges.next();
  }
  private _disabled = false;

  constructor(
    fb: FormBuilder,
    @Optional() @Self() public ngControl: NgControl,
    private fm: FocusMonitor,
    private elRef: ElementRef<HTMLElement>) {

    this.parts = fb.group({'asset1': '', 'asset2': ''});
    // Setting the value accessor directly (instead of using
    // the providers) to avoid running into a circular import.
    if (this.ngControl != null) this.ngControl.valueAccessor = this;

    fm.monitor(elRef.nativeElement, true).subscribe(origin => {
      this.focused = !!origin;
      this.stateChanges.next();
    });

    this.stateChanges.subscribe(() => {
      this.expandInput(this.value.asset1.length);
      if (this.onChangeCallback) {
        this.onChangeCallback(this.value);
        if (this.required) {
          const symbol = this.value;
          if (!symbol.asset1 || !symbol.asset2) {
            this.errorState = true;
          } else {
            this.errorState = false;
          }
        }
      }
    });
  }

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

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

  onKeyup() {
    this.stateChanges.next();
  }

  static ASSET1_INPUT_SIZE = 2;
  asset1InputSize = SymbolInputComponent.ASSET1_INPUT_SIZE;
  expandInput(currentSize) {
    //const currentSize = (event.target as HTMLInputElement).value.length;
    if (currentSize >= 3) {
      this.asset1InputSize = currentSize;
    } else {
      this.asset1InputSize = SymbolInputComponent.ASSET1_INPUT_SIZE;
    }
  }

  writeValue(value: any) {
    this.value = value;
  }

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

  registerOnTouched(fn: any) {
  }

}

符号输入.component.html:

<div [formGroup]="parts" >
  <input class="asset asset1" formControlName="asset1" (keyup)="onKeyup()" [size]="asset1InputSize" maxlength="5">
  <span class="input-spacer">&frasl;</span>
  <input class="asset asset2" formControlName="asset2" size="6" maxlength="5">
</div>

有人会善意地指出我正确的方向吗?

**已更新** symbolInput.invalid标志现在在订阅this.ngControl.valueChanges和设置后设置this.ngControl.control.setErrors

constructor(
    fb: FormBuilder,
    @Optional() @Self() public ngControl: NgControl,
    private fm: FocusMonitor,
    private elRef: ElementRef<HTMLElement>) {

    this.parts = fb.group({'asset1': ['',[Validators.required]], 'asset2': ['',[Validators.required]]});

    if (this.ngControl != null) this.ngControl.valueAccessor = this;

    fm.monitor(elRef.nativeElement, true).subscribe(origin => {
      this.focused = !!origin;
      this.stateChanges.next();
    });
    this.ngControl.valueChanges.subscribe(()=>{
      this.expandInput(this.value.asset1.length);
      if (this.required) {
        if (this.parts.invalid) {
          this.errorState = true;
          this.ngControl.control.setErrors({ "invalidSymbol": true });
        } else {
          this.errorState = false;
          this.ngControl.control.setErrors(null);
        }
      }
    });
    this.stateChanges.subscribe(() => {
      if (this.onChangeCallback) {
        this.onChangeCallback(this.value);
      }
    });
  }

如果您认为这可以改进,请告知。

4

2 回答 2

1

您的实现看起来不错,并且由于您没有添加任何验证,因此您总是得到invalidas 。false

您可以添加验证asset1asset2更改以下行

 this.parts = fb.group({'asset1': '', 'asset2': ''});

 this.parts = this.fb.group({
      asset1: ['', Validators.required],
      asset2: ['', Validators.required,  Validators.minLength(6)]
 });
于 2018-10-30T13:05:52.563 回答
0

我所做的是以下内容:

    get errorState(): boolean {
        return (this.model.invalid && this.model.dirty) 
               || (this.ngControl?.invalid && this.ngControl?.dirty);
    }

model我的本地FormControlngControl父控件在哪里。因此,当我的控件出现错误或父控件无效时,它会返回错误状态。

于 2021-02-23T08:54:34.473 回答