In our application I noticed lots of duplicate code in the HTML of our forms, most input elements have the same structure so I want to create a generic input component that we can use instead to keep the files clean.
The problem I currently have is that there is 1 form that has 2 nested formGroups inside, example:
this.addressForm = this.fb.group({
postalAddress: this.fb.group({
street: ["", [Validators.required]],
}),
visitorAddress: this.fb.group({
street: [""],
})
});
This leads to my new component's HTML to also have duplicate code due to some forms requiring a formGroupName.
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
<div *ngIf="!groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
The code above works fine but as mentioned, I would like to get rid of the duplicate code. I figured this would be a good case for a ng-template but it appears when using a template the nested controls can no longer find the surrounding FormGroup.
Example
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<ng-content *ngTemplateOutlet="content"></ng-content>
</div>
<div *ngIf="!groupName">
<ng-content *ngTemplateOutlet="content"></ng-content>
</div>
<ng-template #content>
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</ng-template>
Has anyone encountered such a situation and if so, what would be a good way to go about this?
UPDATE
After Garbage Collectors answer I refactored my code to the following:
<div [formGroup]="form" *ngIf="form && controlPath && controlName">
<div *ngIf="groupName" [formGroupName]="groupName">
<ng-container [ngTemplateOutlet]="inputTemplate" [ngTemplateOutletContext]="{ templateForm: form, templateFormGroup: groupName, templateControlName: controlName }"></ng-container>
</div>
<div *ngIf="!groupName">
<ng-container [ngTemplateOutlet]="inputTemplate" [ngTemplateOutletContext]="{ templateForm: form, templateControlName: controlName }"></ng-container>
</div>
<ng-template #inputTemplate let-form="templateForm" let-groupName="templateFormGroup" let-controlName="templateControlName">
<div [formGroup]="form" [formGroupName]="groupName">
<mat-form-field class="w-full">
<mat-label>{{ label }}</mat-label>
<input type="text" matInput [formControlName]="controlName" *ngIf="type === 'text'">
<input type="number" matInput [formControlName]="controlName" *ngIf="type === 'number'">
<mat-error *ngIf="form.get(controlPath)?.errors?.['required']">{{ 'error.required' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['email']">{{ 'error.email' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['invalid']">{{ 'error.form.invalid' | i18n }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['minlength']">{{ minLengthError }}</mat-error>
<mat-error *ngIf="form.get(controlPath)?.errors?.['pattern']">{{ patternError }}</mat-error>
<mat-error *ngFor="let message of form.get(controlPath)?.errors?.['messages']">{{ message | i18n }}</mat-error>
</mat-form-field>
</div>
</ng-template>
Though the template now has all variables correctly, it cannot find the control path for non-nested forms. I outputted controlName in the template as a test and it is correct. I expect the error occurring due to the formGroupName being null for non-nested forms.
Error:

