In my parent component, I have a FormGroup
with a FormArray
, and I want to handle that array in a child component. The parent's HTML does this:
<ng-container [formGroup]="formGroup">
<app-child formArrayName="theArrayName">
I assumed in the child I would inject the NgControl
and then have access:
@Component({
...,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => ChildComponent),
multi: true
}
]
})
export class ChildComponent implements ControlValueAccessor {
constructor(private readonly control: NgControl) {
this.formArray = control.control as FormArray<...>
}
I always get a null injector saying there's no provider for NgControl
.
CodePudding user response:
NG_VALUE_ACCESSOR is a provider to create a custom form control not a custom form array. You should define a ChildComponent as a FormControl. Refer this how to do it.
And then you should use FormArray like
<ng-container [formGroup]="formGroup">
<ng-container formArrayName="theArrayName" >
<ng-container *ngFor="let control of formGroup.controls.theArrayName.controls; let i = index">
<app-child [formControlName]="i"></app-child>
</ng-container>
</ng-container>
</ng-container>
CodePudding user response:
Maybe I'm missing some information, but the simplest way to do that is to just use an input property.
parent ts
export class ParentComponent {
formGroup = new FormGroup(...);
get formArray() {
return this.formGroup.get('theArrayName') as FormArray;
}
}
child ts
export class ChildComponent implements OnInit {
@Input() formArray = new FormArray([]);
ngOnInit() {
console.log(this.formArray);
}
}
parent html
<app-child [formArray]="formArray"></app-child>
CodePudding user response:
Julian lin could work, but there is a simple way to do it as well without the ControlValueAccessor if you know you are gonna use a FormGroup instead the array.
So in the ChildComponent you have
export class ChildComponent implements OnInit{
formGroup: FormGroup
constructer (private formCtrl: FormGroupDirective){}
ngOnInit(){
// only available after ngOnInit
this.formGroup = formCtrl.form;
}
}
in the parent component template you would set it something like this
<ng-container *ngFor="let someGroup of formArray.controls">
<app-child-component [formGroup]="someGroup" ></app-child-component>
</ng-container>
then you can use the parent to still detect if there is an validation error in the child forms.
CodePudding user response:
Complementary the Chris Hamilton's answer
<app-child [formArray]="form.get('formArrayNme')"></app-child>
The problem when you mannage a formArray in a child that you pass as input is that you can not use the "typical" constructor of manage FormArrays. You should define a function (*)
//if is a FormArray of FormControls
getControl(index:number)
{
return this.formArray.at(index) as FormControl
}
//if is a FormArray of FormGroup
getGroup(index:number)
{
return this.formArray.at(index) as FormGroup
}
And use
<!--if is a formArray of FormControls-->
<div *ngFor="let control of FormArray;let i=index">
<input [formControl]=getControl(i)>
</div>
<!--if is a formArray of FormGroups-->
<div *ngFor="let group of FormArray;let i=index" [formGroup]="getGroup(i)>
<input formControlName="prop1">
<input formControlName="prop2">
</div>
If we want to use the typical FormArray with formArrayName we need viewProvider the FormGroupDirective and know the name. We can do using a child-control like
@Component({
selector: 'child-array',
templateUrl: 'child-array.html',
viewProviders:[
{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class ChildComponent {
array:FormArray
arrayName:string="fool"
@Input() name: string;
@Input('array') set _(value)
{
this.array=value as FormArray
this.arrayName=Object.keys(this.array.parent.controls)
.find(key=>this.array.parent.get(key)==this.array)
}
}
Now we can use
<!--if is a formArray of FormControls-->
<div [formArrayName]="arrayName">
<div *ngFor="let control of array.controls;let i=index">
<input [formControlName]="i">
</div>
</div>
<!--if is a formArray of FormGroups-->
<div [formArrayName]="arrayName">
<div *ngFor="let control of array.controls;let i=index" [formGroupName]="i">
<input formControlName="prop1">
<input formControlName="prop2">
</div>
</div>
This second approach (in the case of formArray of FormControls) can be see in this stackblitz
(*)I know that some authors use the variable of the loop to get the value of the formControl or the FormGroup
<div *ngFor="let group of formArray.controls" [formGroup]="group">
Unfortunaly, this don't work since Angular 12, because group is only an "AbstractControl". If you has strict mode you received an error saying you that an AbstractControl is not a FormGroup (works in early Angular versions).
Some suggest use the $any, but (personal opinion) is a "ugly work-around" or even de-activate the strict mode (it's a very very very bad idea and this last is not a personal opinion)