angular - 用于包裹 Angular Material 输入的组件不显示错误样式

标签 angular angular-material

我想将 Angular Material 的 matInput 封装在组件中,以便在应用程序的其他位置重用它,因为我需要管理其内部状态以将输入类型从文本更改为密码,反之亦然。

我设法通过实现 ControlValueAccessor 来做到这一点,但验证错误的样式没有显示。

密码字段组件:

export class PasswordFieldComponent
  implements OnInit, ControlValueAccessor {

  @ViewChild(DefaultValueAccessor) private valueAccessor: DefaultValueAccessor;

  @Input() customClass: string;
  @Input() customPlaceholder: string;
  @Input() required = true;

  hide = true;

  constructor() { }

  ngOnInit() {
  }

  private propagateChange = (_: any) => { };

  private onChange(event) {
    this.propagateChange(event.target.value);
  }

  private onTouch() { }

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

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

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

  writeValue(value: any): void {
    this.valueAccessor.writeValue(value);
  }

}

密码字段模板:

<mat-form-field class="full-width {{ customClass }}">

  <input
    matInput
    ngDefaultControl
    placeholder="{{ customPlaceholder }}"
    [required]="required"
    [type]="hide ? 'password' : 'text'"
    (input)="onChange($event)">

  <button mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hide">
    <mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
  </button>

</mat-form-field>

enter image description here

最佳答案

我的评论中的代码是制作“最简单的自定义表单控件,内部有 Material 输入”。这个想法是创建自定义 ErrorStateMatcher 来询问控件本身。因此,内部 Material 输入在无效时不会显示错误,否则在我们的自定义控件无效时会显示错误

此 ErrorStateMatcher 需要了解我们的控件,因此我们将创建一个构造函数来注入(inject)此控件(我在构造函数中注入(inject)另一个对象“错误”以允许使 Material 输入“无效”)

class CustomFieldErrorMatcher implements ErrorStateMatcher {
  constructor(private customControl: FormControl,private errors:any) { }

  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return this.customControl && this.customControl.touched &&(this.customControl.invalid || this.errors);
  }
}

.html 就像

<mat-form-field>

    <input #input="ngModel" [ngModel]="value" (ngModelChange)="value=$event;onChange($event)"
    matInput
    [errorStateMatcher]="errorMatcher()"
    [placeholder]="placeholder"
    [type]="hide ? 'password' : 'text'"
    (blur)="onTouched()"
    >
    <button mat-icon-button matSuffix (click)="hide = !hide" [attr.aria-label]="'Hide password'" [attr.aria-pressed]="hide">
    <mat-icon>{{hide ? 'visibility_off' : 'visibility'}}</mat-icon>
  </button>
    <mat-error *ngIf="control?.errors?.required">
        Please enter a {{placeholder}}
    </mat-error>
    <mat-error *ngIf="errors?.errorMatch">
        Must match
    </mat-error>

</mat-form-field>

最重要的部分是这个

[errorStateMatcher]="errorMatcher()"

请参阅使用 [ngModel] 和 (ngModel), (blur) 将自定义 formControl 标记为“已触摸”。我添加了一个 mat-error *ngIf="errors?.errorMatch。这是一个 @Input() ,它获取 Form 的错误值。这是因为我们制作了一个具有自定义的 FormGroup如果“password”和“repeatpassword”两个字段不匹配,则会出现错误。

我们的自定义表单控件就像

export class CustomSelectComponent implements AfterViewInit, ControlValueAccessor {

  control: FormControl
  onChange: any = () => { };
  onTouched: any = () => { };

  value: any;
  @Input() disabled: boolean;
  @Input() placeholder = '';
  @Input() errors:any=null;

  errorMatcher() {
    return new CustomFieldErrorMatcher(this.control,this.errors)
  }
  constructor(public injector: Injector) {
  }

  ngAfterViewInit(): void {
    const ngControl: NgControl = this.injector.get(NgControl, null);
    if (ngControl) {
      setTimeout(() => {
        this.control = ngControl.control as FormControl;
      })
    }
  }

了解如何在 ngAfterViewInit 中获取 ngControl、errorMatcher() 如何返回新的 CustomFieldErrorMatcher 以及如何传递“control”和“errors”的值。

我们的 app.component 就像

  ngOnInit() {
    this.myForm = new FormGroup(
      {
        password: new FormControl("", Validators.required),
        repeatpassword: new FormControl("", Validators.required)
      },
      this.matchControls("password", "repeatpassword")
    );
  }

  matchControls(field1, field2) {
    return (group: FormGroup) => {
      const control1 = group.get(field1);
      const control2 = group.get(field2);
      return control1 && control2 &&
        control1.value && control2.value &&
        control1.value != control2.value
        ? { errorMatch: "must match" }: null;
    };
  }

app.component 的 .html 是

<form [formGroup]="myForm" autocomplete="off">
    <app-custom-input placeholder="Password" formControlName="password" >
    </app-custom-input>
    <app-custom-input placeholder="Repeat password" formControlName="repeatpassword" [errors]="myForm.errors?.errorMatch?myForm.errors:null" >
    </app-custom-input>
</form>

stackblitz

在自定义组件上添加了此监听器。您还可以执行“模糊”事件。

https://stackoverflow.com/a/59086644/12425844

@HostListener('focusout', ['$event.target'])
  onFocusout() {
    this.onTouched();
  }
And also calling onTouched when setting any value.

 writeValue(value: any) {
    this.onTouched();
    this.Value = value ? value : '';
}

关于angular - 用于包裹 Angular Material 输入的组件不显示错误样式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58459617/

相关文章:

javascript - 在父组件的表单提交中获取子组件表单值?

javascript - 当 % 字符出现在 url 中时出现问题

angular - MS Graph Explorer - 列出加入的团队 - 错误 404

angularjs - 如何在 Angular Material 日期时间选择器中禁用周末

css - 减少 Angular Material 树中的行高

Angular 2 "Expression has changed after it was checked"

javascript - 管道内的映射被忽略并且不会触发http调用

angular - 如何在网格列表中按 Material 使用 ngfor

angularjs - 以编程方式更改 md-select 中的选定项目

Angular : An unhandled exception occurred: Cannot find module 'webpack/lib/ParserHelpers'