Я столкнулся с серьезной проблемой в Angular. Я хочу создать компонент настраиваемой формы, который «связывает» несколько дочерних элементов ввода и действителен, когда допустимы все дочерние элементы.
Я думал, что просто создам компонент, реализующий интерфейсы ControlValueAccessor и Validator. Я бы ввел входные данные с помощью декоратора ViewChildren как NgModels, чтобы я мог перебирать их и собирать любые ошибки проверки в методе проверки компонента. Вот пример реализации компонента ввода адреса, который я сделал, который делает это для двух входов (улица и номер улицы), для которых требуется значение:
import { AfterViewInit, Component, forwardRef, OnInit, QueryList, ViewChildren } from '@angular/core';
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NgModel, NG_VALUE_ACCESSOR, ValidationErrors, Validator } from '@angular/forms';
import { Address } from './address';
@Component({
selector: `address-input`,
template: `
<label for="street">Street</label>
<input id="street" name="street" type="text" [(ngModel)]="Street" (blur)="onTouchedAField()" required /><br />
<label for="number">Number</label>
<input id="number" name="number" type="number" [(ngModel)]="Number" (blur)="onTouchedAField()" required />`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: AddressInputComponent,
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: AddressInputComponent,
multi: true,
},
],
})
export class AddressInputComponent implements ControlValueAccessor, Validator {
@ViewChildren(NgModel) public validatedFields!: QueryList<NgModel>;
private address: Address = new Address(null, null);
private onFormCtrlChanged!: (_: any) => void;
private onFormCtrlTouched!: (_: any) => void;
public get Street(): string | null {
if (this.address) {
return this.address.Street;
} else {
return null;
}
}
public set Street(val: string) {
this.address = new Address(val, this.address.Number);
this.emitChanged();
}
public get Number(): number | null {
if (this.address) {
return this.address.Number;
} else {
return null;
}
}
public set Number(val: number | null) {
this.address = new Address(this.address.Street, val);
this.emitChanged();
}
public onTouchedAField(): void {
this.emitTouched();
}
public writeValue(newAddress: Address): void {
if (newAddress !== undefined) {
this.address = newAddress;
}
}
public registerOnChange(fn: (_: any) => void): void {
this.onFormCtrlChanged = fn;
}
public registerOnTouched(fn: (_: any) => void): void {
this.onFormCtrlTouched = fn;
}
public validate(control: AbstractControl): ValidationErrors | null {
let validationErrors: ValidationErrors | null = null;
this.validatedFields.forEach((ngm: NgModel) => {
console.log(`ValidationErrs for ${ngm.name} with value ${ngm.value}`, ngm.errors);
if (ngm.errors !== null) {
if (validationErrors === null)
validationErrors = {};
validationErrors[ngm.name] = ngm.errors;
}
});
console.log(`validationErrors `, validationErrors);
return validationErrors;
}
private emitChanged(): void {
this.onFormCtrlChanged(this.address);
}
private emitTouched(): void {
this.onFormCtrlTouched(this.address);
}
}
В основном это отлично работает, когда я встраиваю компонент адреса в такую форму:
<form (ngSubmit)="onSubmit()" #myForm="ngForm">
<address-input name="address" [(ngModel)]="address" #addressInput="ngModel"></address-input>
<button type="submit">SUBMIT</button>
</form>
Единственная, но раздражающая проблема заключается в том, что, когда компонент ввода адреса будет правильно заполнен через привязку модели, компонент ввода адреса, тем не менее, останется в недопустимом состоянии.
Т.е. когда компонент приложения, содержащий форму, которая содержит компонент настраиваемой формы, определяет поле адреса, которое он привязывает к компоненту настраиваемой формы, например:
public address: Address = new Address('Baker Street', 123);
компонент настраиваемой формы должен быть действительным, но остается недействительным.
Когда я затем добавляю букву или цифру в поле, например, компонент настраиваемой формы сразу становится действительным.
После некоторого расследования я заметил, что это связано с тем, что Angular вызывает метод проверки интерфейса Validator сразу после того, как он вызывает метод writeValue интерфейса ControlValueAccessor в компоненте настраиваемой формы. Таким образом, значения дочерних входов компонента настраиваемой формы и их состояние проверки еще не обновляются, когда Angular вызывает метод проверки в компоненте настраиваемой формы.
Чтобы решить мою проблему, мне интересно, есть ли способ заставить Angular обновлять значение и состояние проверки ngModels или снова запускать проверку при обновлении дочерних входов / компонентов. Или есть другой способ заставить эту работу работать. В более широком смысле, мне также интересно, есть ли лучший способ реализовать компонент формы, который действителен, когда есть все его дочерние элементы, потому что я чувствую, что для исправления этой проблемы потребуются некоторые «взломы» или неэффективный код.
Здесь вы можете найти Stackblitz, содержащий простое воспроизведение проблемы.
Заранее спасибо за ваше время и помощь, Джошуа