I have understand concept of recursive elements(tree view) from below.
Link 1
In my case, I want to use it with forms; let's say simple text input recursively. JSON structure of form is as below.
JOSN Structure
I have prepared below code.I am getting Maximum call stack size exceeded error by executing below code.
Below is my component.html file.
<form [formGroup]="testForm" (ngSubmit)="onSubmit()">
<div formArrayName="element">
<ng-template #recursiveList let-list>
<div *ngFor="let item of testForm.get('element').controls;let i=index;">
<div [formGroupName]="i">
<input type="text" formControlName="type">
</div>
<!-- {{item.get('element')?.controls?.length}} -->
<div *ngIf="item.get('element')?.controls?.length > 0">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.get('element').controls }"></ng-container>
</div>
</div>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: testForm.get('element').controls }"></ng-container>
</div>
And here is component.ts file.
import { Component, OnInit } from '@angular/core';
import {FormControl, FormGroup, Validators, FormBuilder, FormArray} from '@angular/forms';
@Component({
selector: 'app-test',
templateUrl: './test.component.html',
styleUrls: [
'./test.component.scss'
]
})
export class TestComponent implements OnInit{
testForm:FormGroup;
element:any;
constructor(private formBuilder: FormBuilder) { }
ngOnInit() {
this.testForm=this.formBuilder.group({
element:this.formBuilder.array([
this.formBuilder.group({
type:'',
element:this.formBuilder.array([
this.formBuilder.group({
type:'',
element:this.formBuilder.array([
])
})
])
})
])
})
}
onSubmit() {
console.log(this.testForm.value);
}
}
The FormBuilder service is an injectable provider that is provided with the reactive forms module. We will inject this dependency by adding it to the component constructor. File name : employeeDetails-editor.component.ts. 1constructor(private fb: FormBuilder) { } typescript.
Template Driven Forms are based only on template directives, while Reactive forms are defined programmatically at the level of the component class. Reactive Forms are a better default choice for new applications, as they are more powerful and easier to use.
The FormBuilder provides syntactic sugar that shortens creating instances of a FormControl , FormGroup , or FormArray . It reduces the amount of boilerplate needed to build complex forms.
In fact, Bhavik Patel's answer will solve the problem with the error:
Maximum call stack size exceeded
But your form bindings won't work properly due to how Angular reactive forms work. It relies on Dependency Injection tree which for this template looks like:
FormGroup(testForm)
|__ FormArrayName('element')
|___ FormGroupName('0')
| ....
|___ FormGroupName(n)
You will always get updates only on your top level controls.
To fix this issue you can define some prefix which will be updated inside embedded view. And then use that prefix to define formGroup
on each level of your tree:
<form class="tree" [formGroup]="testForm" (ngSubmit)="onSubmit()">
<ng-template #recursiveList let-controls let-prefix="prefix">
<ng-container *ngFor="let item of controls; let i = index">
<div class="tree-item" [formGroup]="testForm.get(prefix + i)">
<input type="text" formControlName="type">
</div>
<div class="sub-tree" *ngIf="item.get('element')?.controls?.length">
<ng-container
*ngTemplateOutlet="recursiveList; context:{ $implicit: item.get('element').controls, prefix: prefix + i + '.element.' }"></ng-container>
</div>
</ng-container>
</ng-template>
<ng-container
*ngTemplateOutlet="recursiveList; context:{ $implicit: testForm.get('element').controls, prefix: 'element.' }"></ng-container>
</form>
Ng-run Example
There are some changes in your HTML code as you are looking for the element list instead of list property itself.
Here is the updated code after resolving the issue.
component.html
<form [formGroup]="testForm" (ngSubmit)="onSubmit()">
<div formArrayName="element">
<ng-template #recursiveList let-element>
<div *ngFor="let item of element;let i=index;">
<div [formGroupName]="i">
<input type="text" formControlName="type">
</div>
<!-- {{item.get('element')?.controls?.length}} -->
<div *ngIf="item.get('element')?.controls?.length > 0">
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: item.get('element').controls }"></ng-container>
</div>
</div>
</ng-template>
<ng-container *ngTemplateOutlet="recursiveList; context:{ $implicit: testForm.get('element').controls }"></ng-container>
</div>
</form>
P.S.: And of course you should refactor your ts code as well. It should be dynamically generated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With