Is there a way to bind a projected template (ContentChild) to the form defined on the child i.e. add formControlName
once rendered?
Struggling to know what to search for online as I may be using the wrong search terms.
I have a form array (a particular example) below hooked up to a control value accessor,
The component has form group of type FormGroup<{ formArray: FormArray<{ formControl : FormControl }> }
but on value changes I emit just the array of values i.e. formValue.formArray.map(v => v.formControl)
So formArray
and formControl
are just holding names internally on the component.
Particular example
<fieldset [formGroup]="formGroup">
<legend>Paving runs (minimum 1)</legend>
<ng-container formArrayName="formArray">
<fieldset *ngFor="let frmGrp of formGroup.controls.formArray.controls; let i = index">
<legend>Paving run</legend>
<ng-container [formGroup]="frmGrp">
<!-- what I want to project in a reusable version-->
<paving-run-form
formControlName="formControl">
</paving-run-form>
</ng-container>
<button (click)="remove(i)">Delete run</button>
</fieldset>
</ng-container>
<button (click)="add()">Add run</button>
</fieldset>
This is fairly standard and works but now I'm trying to create a reuseable component version of this.
So that I can do something like:
<generic-form-array formControlName="paved_runs" [labels]="labels">
<ng-template editMode>
<paving-run-form></paving-run-form>
</ng-template>
</generic-form-array>
where labels is in this case would be
@Input() labels: GenericFormArrayOptions = {
legendMany: "Paving runs (minimum 1)",
legendOne: "Paving run",
deleteOne: "Delete run",
addOne: "Add run",
}
<fieldset [formGroup]="formGroup">
<legend>{{ labels.legendMany }}</legend>
<ng-container formArrayName="formArray">
<fieldset *ngFor="let frmGrp of formGroup.controls.formArray.controls; let i = index">
<legend>{{ labels.legendOne }}</legend>
<ng-container [formGroup]="frmGrp">
<!-- project the form to edit one of array elements -->
<ng-container
[ngTemplateOutlet]="editModeTpl.templateRef">
</ng-container>
</ng-container>
<button (click)="remove(i)">{{ labels.deleteOne }}</button>
</fieldset>
</ng-container>
<button (click)="add()">{{ labels.addOne }}</button>
</fieldset>
With the following defined on the class:
@ContentChild(EditModeDirective) editModeTpl!: EditModeDirective
i.e. uses the directive
import { Directive, TemplateRef } from '@angular/core';
@Directive({
selector: '[editMode]'
})
export class EditModeDirective {
constructor(public templateRef: TemplateRef<any>) {}
}
In this case the content is projected as expected but <paving-run-form></paving-run-form>
isn't bound to the child form. Trying <paving-run-form formControlName="formControl"></paving-run-form>
doesn't work because this is expecting formControl
on the parent.
Is there some way to render the component and then add it to the child form?
Very typical that I solve just after asking the question...
In the reuseable component I need to pass the form group via the context
<ng-container
[ngTemplateOutlet]="editModeTpl.templateRef"
[ngTemplateOutletContext]="{formGroup: frmGrp}">
</ng-container>
and when using as a customer I need to do the following:
<generic-form-array formControlName="paved_runs">
<ng-template editMode let-formGroup="formGroup">
<paving-run-form [formControl]="formGroup.get('formControl')"></paving-run-form>
</ng-template>
</generic-form-array>
Thanks to @Eliseo for leaving a helpful comment on a question I can no longer find :)