Я думаю, вам нужно добавить multi: true
к вашему NG_VALIDATORS
токену.
Кроме того, похоже, что ваш ViewBoxFormComponent
не является простым FormControl
, потому что внутри вашего компонента у вас есть другой экземпляр FormGroup
. ИМО, это не совсем правильно.
FormControlDirective
, FormControlName
, FormGroupName
, FormArrayName
, FormGroupDirective
- это директивы, основанные на управлении формой, и при объединении они образуют AbstractControlDirective
дерево (потому что каждый наследует от AbstractControlDirective
). Эти директивы являются мостом между уровнем представления (обрабатывается ControlValueAccessor
) и уровнем модели (обрабатывается AbstractControl
- базовый класс для FormControl
, FormGroup
, FormArray
).
root такого дерева FormGroupDirective
(обычно привязано к элементу form
: <form [formGroup]="formGroupInstance">
). Имея это в виду, ваше дерево AbstractControl
будет выглядеть так:
FG <- [formGroup]="SvgFormData"
|
FC <- formGroupName="viewBox"
/ | \
/ | \
FC FC FC (height, width, x etc...)
У FormControl
не должно быть потомков. В этом случае, потому что ваш пользовательский компонент (ViewBoxFormComponent
) создает другое дерево. Это не неправильно , но при таком подходе вам придется правильно обрабатывать оба дерева, чтобы не получить неожиданные результаты.
Экземпляр FormGroup
, связанный с <section [formGroup]="ViewBoxFormData" class="text-control-section">
не будет зарегистрировано как потомок SvgFormData
, поскольку FormGroupDirective
не ведет себя как FormGroupName
:
constructor(
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
}
constructor(
@Optional() @Host() @SkipSelf() parent: ControlContainer, // `ControlContainer` - token for `FormGroupDirective`/`FormGroupName`/`FormArrayName`
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
super();
this._parent = parent;
this._validators = validators;
this._asyncValidators = asyncValidators;
}
Если ваш пользовательский компонент не должен содержать простой экземпляр FormControl
(он обрабатывает только автономный FormControl
экземпляр с помощью директив formControl
или ngModel
), вы должны зарегистрировать его как группу форм-потомков.
Это может быть достигнуто следующим способом:
parent.component.ts
// The shape of the form is defined in one place!
// The children must only comply with the shape defined here
this.SvgFormData = this.fb.group({
ViewBoxFormData: this.fb.group({
height: this.fb.control(''),
width: this.fb.control(''),
/* ... */
})
})
parent.component. html
<form [formGroup]="SvgFormData" (ngSubmit)="onSubmit()">
<!-- this will be a child form group -->
<view-box-form-component></view-box-form-component>
</form>
view-box-form-component.component.ts
@Component({
selector: 'view-box-form-component',
templateUrl: './view-box-form.component.html',
styleUrls: ['./view-box-form.component.css'],
providers: [
{
provide: NG_VALIDATORS,
useExisting: forwardRef(()=>ViewBoxFormComponent),
multi: true,
}
],
viewProviders: [
{ provide: ControlContainer, useExisting: FormGroupDirective }
]
})
export class ViewBoxFormComponent implements OnInit {
constructor() { }
ngOnInit() {}
validate(c: AbstractControl): ValidationErrors | null{
/* ... */
}
}
Как видите, в этом нет необходимости предоставить CONTROL_VALUE_ACCESSOR
больше, потому что форма формы будет определена только в одном месте (то есть: контейнер родительской формы). Все, что вам нужно сделать в вашем дочернем контейнере формы, это предоставить правильные пути, которые соответствуют форме, определенной в родительском:
view-box-form-component.component. html
<section formGroupName="ViewBoxFormData" class="text-control-section">
<label class="control-label">
<p class="smallText"> x: </p>
<input type="text" class="text-control smallText" formControlName="x" />
</label>
<label class="control-label">
<p class="smallText"> y: </p>
<input type="y" class="text-control smallText" formControlName="y" />
</label>
<label class="control-label">
<p class="smallText"> width: </p>
<input type="text" class="text-control smallText" formControlName="width" />
</label>
<label class="control-label">
<p class="smallText"> height: </p>
<input type="text" class="text-control smallText" formControlName="height" />
</label>
</section>
Что viewProviders
позволяет вам использовать токен (в данном случае ControlContainer
), который объявлен для элемента host:
// FormGroupName
constructor (@Optional() @Host() @SkipSelf() parent: ControlContainer,) {}
Примечание: тот же подход может быть примененным к FormArrayName
.
Вы можете найти больше о AbstractControl
дереве и о том, как все работает вместе, в Тщательное исследование Angular Формы .