Компонент для ввода углового материала не отображает стили ошибок - PullRequest
0 голосов
/ 19 октября 2019

Я хочу заключить 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

1 Ответ

0 голосов
/ 20 октября 2019

Код из моих комментариев - сделать «самый простой пользовательский элемент управления формой, внутри которого есть материал». Идея заключается в создании пользовательского ErrorStateMatcher, который спрашивает о самом элементе управления. Таким образом, внутренний ввод материала показывает ошибки, а не когда он был недопустим, иначе, когда наш пользовательский элемент управления был недействительным

Этот ErrorStateMatcher должен знать о нашем элементе управления, поэтому мы собираемся создать конструктор для внедрения этого элемента управления (я добавляюв конструкторе другой объект «ошибки», чтобы сделать «недопустимым» ввод материала)

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, в которой есть пользовательская ошибка, если два поля" пароль "и «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;
      })
    }
  }

Посмотрите, как получить ngControl в ngAfterViewInit, как 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;
    };
  }

.html app.component равен

<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

...