Reputation: 21
I'm actually trying to generate reactive forms from data. I would like to create a formGroup and its respective nested forms from json data and also auto generate html form using templates. You will see next the steps i performed with data used and typescript ahd html form creation without templates (works) and with templates (don't work). I feel like ng-templateoutlet don't get the main and nested form context and i don't really succeed to find how pass the main and nested forms in the template context to make it work. If you have already tackle this issue or have been in the same situation I would really appreciate your help. Thank you!
Here is the data used to generate formgroup and html forms:
{
"user":{
"name": "string",
"mail":"string",
"hobbies":{
"sport":"string",
"musique":"string",
"games":"string"
}
},
"house":{
"adress":"string",
"size":"string",
"rooms":{
"kitchen":"string",
"bedrooms":{
"bedroom1":"string",
"bedroom2":"string"
}
}
}
}
I then generated a typescript code that allows generating the correspondig form :
objectform(data:any): FormGroup{
let newForm=this.fb.group({})
for (let item of Object.keys(data)){
if (data[item]=="string"){
newForm.addControl(item, this.fb.control('',[]))
} else {
newForm.addControl(item,this.objectform(data[item]));
}
}
return newForm;
}
ngOnInit():void {
this.testForm = this.fb.group({});
for (let item of Object.keys(this.data)){
if (this.data[item]!="string"){
this.testForm.addControl(item,this.objectform(this.data[item]));
} else{
this.testForm.addControl(item, this.fb.control('',[]))
}
}
console.log("testForm: ",this.testForm)
}
The formgroup produced respect the structure of the data.
Here is the html code made by hand that works with this data:
<div>
<form [formGroup]="testForm">
<div formGroupName="user">
<h1>User Infos:</h1>
<mat-form-field appearance="fill" >
<label>name :</label>
<textarea matInput formControlName="name"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" >
<label>mail :</label>
<textarea matInput formControlName="mail"></textarea>
</mat-form-field>
<div formGroupName="hobbies">
<h2>Hobbies:</h2>
<mat-form-field appearance="fill" >
<label>sport :</label>
<textarea matInput formControlName="sport"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" >
<label>musique :</label>
<textarea matInput formControlName="musique"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" >
<label>games :</label>
<textarea matInput formControlName="games"></textarea>
</mat-form-field>
</div>
</div>
<div formGroupName="house">
<h1>House Infos:</h1>
<mat-form-field appearance="fill" >
<label>adress :</label>
<textarea matInput formControlName="adress"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" >
<label>size :</label>
<textarea matInput formControlName="size"></textarea>
</mat-form-field>
<div formGroupName="rooms">
<h2>Rooms:</h2>
<mat-form-field appearance="fill" >
<label>kitchen :</label>
<textarea matInput formControlName="kitchen"></textarea>
</mat-form-field>
<div formGroupName="bedrooms">
<h2>bedrooms:</h2>
<mat-form-field appearance="fill" >
<label>bedroom1 :</label>
<textarea matInput formControlName="bedroom1"></textarea>
</mat-form-field>
<mat-form-field appearance="fill" >
<label>bedroom2 :</label>
<textarea matInput formControlName="bedroom2"></textarea>
</mat-form-field>
</div>
</div>
</div>
</form>
<button mat-raised-button (click)="onSubmit()">Submit</button>
</div>
the html code respect the order of nested formgroup so the binding between html form and the one generated in typescript are well synchonized.
Here is the html code used with templates (doesn't work) : note : getKeys function is just the result of Object.keys(object) that was not working directly on html.
<div>
<form [formGroup]="testForm">
<div [formGroup]="testForm">
<ng-container *ngFor="let itemname of getKeys(data)">
<h1>{{itemname}} :</h1>
<ng-container *ngIf="data[itemname] as item">
<ng-container *ngIf="item == 'string'">
<ng-container *ngTemplateOutlet="stringparameter; context: {parametername :itemname}"></ng-container>
</ng-container>
<ng-container *ngIf="item != 'string'" style="padding: 50px">
<div formGroupName="{{itemname}}">
<ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item}"></ng-container>
</div>
</ng-container>
</ng-container>
</ng-container>
</div>
<ng-template #stringparameter let-parametername="parametername">
<mat-form-field appearance="fill" >
<label>{{parametername}}</label>
<textarea matInput formControlName="{{parametername}}"></textarea>
</mat-form-field>
</ng-template>
<ng-template #objectparameter let-parameter="parameter">
<ng-container *ngFor="let itemname of getKeys(parameter)">
<h2>{{itemname}} :</h2>
<ng-container *ngIf="parameter[itemname] as item">
<ng-container *ngIf="item == 'string'">
<ng-container *ngTemplateOutlet="stringparameter; context: {parametername :itemname}"></ng-container>
</ng-container>
<ng-container *ngIf="item != 'string'" style="padding: 50px">
<div formGroupName="{{itemname}}">
<ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item}"></ng-container>
</div>
</ng-container>
</ng-container>
</ng-container>
</ng-template>
</form>
<button mat-raised-button (click)="onSubmit()">Submit</button>
</div> -->
when using this html generation with templates, the console logs shows that html form and typescript formgroup are note well synchronized and return the error :
ROR Error: Cannot find control with name: 'name'
ROR Error: Cannot find control with name: 'mail'
ROR Error: Cannot find control with name: 'hobbies'
ROR Error: Cannot find control with name: 'sport'
ROR Error: Cannot find control with name: 'musique'
etc.
I then would like to know what to pass into the template context that allows it to get the form context and create the great html form synchonized with the typescript formGroup.
Thanks for your help.
Upvotes: 0
Views: 984
Reputation: 21
for those who got the same issue, i found the solution. When you use forms with ng-template you need to pass into the ng-template context the Parent form. You can then get the formcontrols using parentform.get(formcontrolname). The code below shows the solution to the previous code.
<div>
<form [formGroup]="testForm">
<ng-container *ngFor="let itemname of getKeys(data)">
<h1>{{itemname}} :</h1>
<ng-container *ngIf="data[itemname] as item">
<ng-container *ngIf="item == 'string'">
<ng-container *ngTemplateOutlet="stringparameter; context: {parametername :itemname ,form:testForm}"></ng-container>
</ng-container>
<ng-container *ngIf="item!= 'string'" style="padding: 50px">
<ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item, parametername: itemname, form:testForm.get(itemname)}"></ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-template #stringparameter let-parametername="parametername" let-form="form" >
<div [formGroup]=form>
<mat-form-field appearance="fill" >
<label>{{parametername}}</label>
<textarea matInput [formControl]=form.get(parametername)></textarea>
</mat-form-field>
</div>
</ng-template>
<ng-template #objectparameter let-parameter="parameter" let-parametername="parametername" let-form="form">
<div [formGroup]=form>
<ng-container [formGroup]=form *ngFor="let itemname of getKeys(parameter)">
<h2>{{itemname}} :</h2>
<ng-container *ngIf="parameter[itemname] as item">
<ng-container *ngIf="item == 'string'">
<mat-form-field appearance="fill" >
<label>{{itemname}}</label>
<textarea matInput [formControl]=form.get(itemname)></textarea>
</mat-form-field>
</ng-container>
<ng-container *ngIf="item != 'string'" style="padding: 50px">
<div [formGroup]=form>
<ng-container *ngTemplateOutlet="objectparameter; context: {parameter :item , parametername:itemname, form:form.get(itemname)}"></ng-container>
</div>
</ng-container>
</ng-container>
</ng-container>
</div>
</ng-template>
</form>
<button mat-raised-button (click)="onSubmit()">Submit</button>
</div>
Upvotes: 2