简体   繁体   English

Angular Material - 带有错误状态的自定义反应式表单控件

[英]Angular Material - Custom Reactive Form Control with Error State

I am following this tutorial from the Angular Material website about creating a custom form control.我正在关注来自 Angular Material 网站的有关创建自定义表单控件的教程 There are no examples in the tutorial regarding how to respect form control errors when there are validation errors.本教程中没有关于如何在出现验证错误时尊重表单控件错误的示例。

custom-text.component.html自定义text.component.html

<mat-form-field class="example-full-width">
    <mat-select [placeholder]="placeholder" #select [formControl]="control">
        <mat-option *ngFor="let food of foods" [value]="food">
            {{food}}
        </mat-option>
    </mat-select>
    <mat-error>This is required.</mat-error>
</mat-form-field>

custom-text.component.ts自定义text.component.ts

import { Component, ViewChild, HostBinding, Input, ChangeDetectionStrategy, Optional, Self, DoCheck, OnInit, NgZone } from '@angular/core';
import { ControlValueAccessor, NgControl, NgForm, FormGroupDirective, FormControlDirective, FormControlName, FormControl, FormBuilder } from '@angular/forms';
import { MatFormFieldControl, MatSelect, CanUpdateErrorState, ErrorStateMatcher } from '@angular/material';

@Component({
    selector: 'custom-text',
    templateUrl: './custom-text.component.html',
    styleUrls: [
        './custom-text.component.scss'
    ],
    providers: [
        {
            provide: MatFormFieldControl,
            useExisting: CustomTextComponent
        }
    ],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomTextComponent implements ControlValueAccessor, OnInit, DoCheck {

    @Input()
    foods: string[];

    @Input()
    get errorStateMatcher(): ErrorStateMatcher {
        return this.select.errorStateMatcher;
    }
    set errorStateMatcher(val) {
        this.select.errorStateMatcher = val;
    }

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

    @Input()
    get value() {
        return this.select.value;
    }
    set value(val) {
        this.select.value = val;
        this.stateChanges.next();
    }

    @ViewChild('select')
    select: MatSelect;

    control: FormControl;

    constructor(
        @Optional() @Self() ngControl: NgControl,
        @Optional() private _controlName: FormControlName) {
        if (ngControl) {
            ngControl.valueAccessor = this;
        }
    }

    ngOnInit(): void {
        this.control = this._controlName.control;
    }

    ngDoCheck(): void {
        this.select.updateErrorState();
    }

    writeValue(obj: any): void {
        this.value = obj;
    }
    registerOnChange(fn: any): void {
        this.select.registerOnChange(fn);
    }
    registerOnTouched(fn: any): void {
        this.select.registerOnTouched(fn);
    }
    setDisabledState?(isDisabled: boolean): void {
        this.select.setDisabledState(isDisabled);
    }


}

app.component.html应用程序组件.html

<div style="text-align:center">
  <form class="example-form" [formGroup]="myForm" (submit)="submitForm()">
    <custom-text [foods]="[null, 'burger', 'spaghetti', 'fries']" 
      formControlName="selectedFood" 
      [errorStateMatcher]="matcher"></custom-text>
    <button>Submit</button>
  </form>
</div>

Basically, I injected FormControlName instance into the custom control.基本上,我将FormControlName实例注入到自定义控件中。

constructor(
    @Optional() private _controlName: FormControlName) {
....
ngOnInit(): void {
    this.control = this._controlName.control;
}

I then bind it's control property into the inner mat-select control.然后我将它的control属性绑定到内部mat-select控件中。

<mat-select [placeholder]="placeholder" #select [formControl]="control">

I then call this.select.updateErrorState inside ngDoCheck .然后我打电话this.select.updateErrorStatengDoCheck

Here's the link to StackBlitz: https://stackblitz.com/edit/angular-c4ufpp这是 StackBlitz 的链接: https ://stackblitz.com/edit/angular-c4ufpp

Is there is a better or more standard way of this?有没有更好或更标准的方法?

Instead of using the Validator interface, which create the cyclic error dependency, add errorState in your custom component that checks the ngControl that was injected into the constructor, like this: 而不是使用创建循环错误依赖项的Validator接口,在自定义组件中添加errorState ,以检查注入构造函数的ngControl ,如下所示:

get errorState() {
  return this.ngControl.errors !== null && !!this.ngControl.touched;
}

This makes DoCheck unnecessary. 这使DoCheck不必要。 In your parent component, instead of using errorMatcher , use normal Angular validators, like this: 在父组件中,使用普通的Angular验证器,而不是使用errorMatcher ,如下所示:

selectedFood = new FormControl('burger', [Validators.required]);

You could implement the validator Interface. 您可以实现验证器接口。

import { NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator, Validators } from'@angular/forms';
..other imports
    @Component({
      ...
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => YourComponent),
          multi: true
        },
        {
          provide: NG_VALIDATORS,
          useExisting: forwardRef(() => YourComponent),
          multi: true,
        }
      ]
    })

       validate(c: AbstractControl): ValidationErrors | null {
        ... put your validation check in here and return null if it´s valid
      }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM