Angular ControlValueAccessor обязательный атрибут

У меня есть текстовое поле управления, который реализует ControlValueAccessor. В форме я поместил обязательный валидатор в имя поля формы:

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

В шаблоне я использую элемент управления следующим образом:

<input-text formControlName="name"></input>

Проверка Angular обязательна, но внутреннее текстовое поле не имеет обязательного атрибута. Я хочу стилизовать это текстовое поле, если оно требуется, как мне это сделать?

3 ответа

2023 ответ

Вставьте элемент управления формой родительского компонента в пользовательский элемент управления, затем вызовитеhasValidator(...)в родительском элементе управления следующим образом:

        // your custom input component...

  public get isRequired(): boolean {
    return (
      !!this.parent &&
      !!this.parent.control &&
      this.parent.control.hasValidator(Validators.required)
    );
  }

  constructor(@Self() @Optional() private parent: NgControl) {
    this.parent.valueAccessor = this;
  }

  // ...

Полный пример

В этом примере показано, как отображать звездочку при наличии необходимого валидатора.

Шаблон страницы регистрации:

      <form [formGroup]="fg">
    <app-text-input
      [label]="'signup.name' | translate"
      formControlName="name"
    ></app-text-input>
    <app-text-input
      [label]="'signup.email' | translate"
      formControlName="email"
    ></app-text-input>
</form>

Компонент SignupPage:

      import { Component } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';

@Component({
  selector: 'app-signup',
  templateUrl: './signup.page.html',
  styleUrls: ['./signup.page.scss'],
})
export class SignupPage {
  public readonly fg = new FormGroup({
    name: new FormControl<string>('', [Validators.required]),
    email: new FormControl<string>('', [Validators.required, Validators.email]),
  });
}

Шаблон TextInput:

      <input [formControl]="control" (blur)="onBlur()" />
<label *ngIf="label">
  {{ label }}
  <ng-container *ngIf="isRequired">*</ng-container>
</label>

Компонент TextInput:

      import {
  Component,
  HostBinding,
  Input,
  OnDestroy,
  OnInit,
  Optional,
  Self,
} from '@angular/core';
import {
  ControlValueAccessor,
  FormControl,
  NgControl,
  Validators,
} from '@angular/forms';
import { Subject, takeUntil, tap } from 'rxjs';

@Component({
  selector: 'app-text-input',
  templateUrl: './text-input.component.html',
  styleUrls: ['./text-input.component.scss'],
})
export class TextInputComponent
  implements OnInit, OnDestroy, ControlValueAccessor
{
  @Input()
  public label?: string;

  public get isRequired(): boolean {
    return (
      !!this.parent &&
      !!this.parent.control &&
      this.parent.control.hasValidator(Validators.required)
    );
  }

  public readonly control = new FormControl<string>('');

  private onTouched?: () => void;
  private onChanges?: (v: string | null) => void;
  private readonly onDestroy$ = new Subject<void>();

  constructor(@Self() @Optional() private parent: NgControl) {
    this.parent.valueAccessor = this;
  }

  public ngOnInit(): void {
    this.control.valueChanges
      .pipe(
        tap((e) => {
          if (this.onChanges) {
            this.onChanges(e);
          }
        }),
        takeUntil(this.onDestroy$)
      )
      .subscribe();
  }

  public ngOnDestroy(): void {
    this.onDestroy$.next();
    this.onDestroy$.complete();
  }

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

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

  public writeValue(value: string | null): void {
    this.control.setValue(value, { emitEvent: false });
  }

  public setDisabledState(value: boolean): void {
    if (value) {
      this.control.disable();
    } else {
      this.control.enable();
    }
  }

  public onBlur(): void {
    if (this.onTouched) {
      this.onTouched();
    }
  }
}

Вы должны проверить изменение значения элемента управления формы, как показано ниже

this.control.valueChanges.subscribe(
            (res) => {
                this.setErrorMessage(this.control);
            }
        )

А затем внутри setErrorMessage проверьте control.errors

if (control.errors) {
  for (let propertyName in control.errors) {
    if(propertyName == "required") {
      // you can do things here to your input field by using element reference
    }
  }
}

У меня был создан, чтобы функционировать, чтобы знать, является ли поле ввода грязь = у или нет

//Validate single field
export function isInputFieldDirty(controlName: string, formGroupObject: FormGroup) {
if (formGroupObject.get(controlName)) {
    return !formGroupObject.get(controlName).pristine &&
        ((formGroupObject.get(controlName).untouched && formGroupObject.get(controlName).dirty && formGroupObject.get(controlName).invalid) ||
            (formGroupObject.get(controlName).touched && formGroupObject.get(controlName).invalid))
        ? true : false;
}

}

Если вы хотите стилизовать поле, если у контроллера есть требуемый валидатор, вы можете использовать эту утилиту:

  isRequired(formControl: AbstractControl): boolean {
    return this.hasRequiredField(formControl);
  }
  hasRequiredField = (abstractControl: AbstractControl): boolean => {
    // caso formControl
    if (abstractControl.validator) {
      const validator = abstractControl.validator({} as AbstractControl);
      if (validator && validator.required) {
        return true;
      }
    } // caso formGroup
    if (abstractControl['controls']) {
      for (const controlName in abstractControl['controls']) {
        if (abstractControl['controls'][controlName]) {
          if (this.hasRequiredField(abstractControl['controls'][controlName])) {
            return true;
          }
        }
      }
    }
    return false;
  }

в вашем контроллере

isRequired(formControlName){
    isRequired(formControlName: string): boolean {
        return this.utils.isRequired(this.form.get(formControlName));
    }
}

если вы хотите показать сообщение, когда поле недопустимо, и применить стиль, вы можете использовать просто свойство "valid". Для отображения ошибки сообщения, когда поле недопустимо, объект возвращается в hasError('required')

Теперь, если вы хотите, чтобы стиль

<label> Name {{isRequired('name') ? '*' :'' }} </label>
<input-text formControlName="name" [ngClass]="{'required': isRequired('name'), 'notValid' : !this.form.get('name').valid  }"></input>

<span class="help-block error" *ngIf="((form.get('name').touched || form.get('name').dirty) && !form.get('name').valid)">
        <span *ngIf="form.get('name').hasError('required')">
            {{ 'ERROR_MSG.REQUIRED' | translate }}
        </span>
        <span *ngIf="form.get('name').hasError('maxlength')">
            {{ 'ERROR_MSG.MAXLENGTH' | translate }} {{getError('maxlength').requiredLength}}
        </span>
        <span *ngIf="form.get('name').hasError('minlength')">
            {{ 'ERROR_MSG.MINLENGTH' | translate }} {{getError('minlength').requiredLength}}
        </span>

        <span *ngIf="form.get('name').hasError('myCustomError')">
            {{ 'ERROR_MSG.CUSTOM' | translate }}
        </span>
    </span>
</div>

getError(error: string) {
    return this.form.controls['name'].errors[error];
  }
Другие вопросы по тегам