I am trying to create a dynamic angular reactive form. And able to construct a form fields with the below code, but unable to edit the form controls.
export class BillerDetailsConfirmComponent implements OnInit {
billerForm!: FormGroup;
constructor(
private router: Router,
private dialogService: NbDialogService,
private fb: FormBuilder,
private dashboardService: DashboardService,
private subScriptionService: SubscriptionService,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
this.initializeForm();
this.getCompleteBillerDetails();
}
initializeForm(): void {
this.billerForm = this.fb.group({
billerDetails: this.fb.array([])
});
}
getCompleteBillerDetails(): void {
const billersSubscription = this.dashboardService.getAllBBPSBillers().subscribe(
(respData: any) => {
if (respData.respCode === '00') {
console.log('API Response:', respData.response);
this.populateFormArray(this.billerDetails, respData.response.billerDetails);
}
},
(error: any) => {
console.error('Error fetching biller details:', error);
}
);
this.cdr.detectChanges();
this.subScriptionService.add("billerCategorySubscription", billersSubscription);
}
populateFormArray(formArray: FormArray, data: any[]): void {
formArray.clear();
data.forEach(item => {
const formGroup = this.createFormGroup(item);
formArray.push(formGroup);
});
console.log('Form Array:', formArray);
}
createFormGroup(item: any): FormGroup {
const formGroup: { [key: string]: FormControl } = {};
Object.entries(item).forEach(([key, value]) => {
if (typeof value !== 'object') {
formGroup[key] = new FormControl(value ?? ''); // Ensure controls are created and enabled.
}
});
return this.fb.group(formGroup);
}
get billerDetails(): FormArray {
return this.billerForm.get('billerDetails') as FormArray;
}
getFormControlKeys(group: AbstractControl): string[] {
if (group instanceof FormGroup) {
return Object.keys(group.controls).filter(key =>
group.get(key) instanceof FormControl
);
}
return [];
}
getFieldChunks(keys: string[], size: number): string[][] {
const result = [];
for (let i = 0; i < keys.length; i += size) {
result.push(keys.slice(i, i + size));
}
return result;
}
}
View
<nb-tab tabTitle="Biller Details">
<div class="block-body">
<form [formGroup]="billerForm">
<div *ngFor="let billerGroup of billerDetails.controls; let i = index" [formGroup]="billerGroup">
<ng-container *ngIf="billerGroup?.controls">
<ng-container *ngFor="let chunk of getFieldChunks(getFormControlKeys(billerGroup), 3)">
<div class="row mb-4 justify-content-start">
<div class="col-md-4 d-flex flex-column" *ngFor="let key of chunk">
<label class="form-label">{{ key | titlecase }}</label>
<input nbInput class="w-100" [formControlName]="key" placeholder="Enter {{ key }}" fieldSize="large" />
</div>
</div>
</ng-container>
</ng-container>
</div>
</form>
</div>
</nb-tab>
I am trying to create a dynamic angular reactive form. And able to construct a form fields with the below code, but unable to edit the form controls.
export class BillerDetailsConfirmComponent implements OnInit {
billerForm!: FormGroup;
constructor(
private router: Router,
private dialogService: NbDialogService,
private fb: FormBuilder,
private dashboardService: DashboardService,
private subScriptionService: SubscriptionService,
private cdr: ChangeDetectorRef
) {}
ngOnInit(): void {
this.initializeForm();
this.getCompleteBillerDetails();
}
initializeForm(): void {
this.billerForm = this.fb.group({
billerDetails: this.fb.array([])
});
}
getCompleteBillerDetails(): void {
const billersSubscription = this.dashboardService.getAllBBPSBillers().subscribe(
(respData: any) => {
if (respData.respCode === '00') {
console.log('API Response:', respData.response);
this.populateFormArray(this.billerDetails, respData.response.billerDetails);
}
},
(error: any) => {
console.error('Error fetching biller details:', error);
}
);
this.cdr.detectChanges();
this.subScriptionService.add("billerCategorySubscription", billersSubscription);
}
populateFormArray(formArray: FormArray, data: any[]): void {
formArray.clear();
data.forEach(item => {
const formGroup = this.createFormGroup(item);
formArray.push(formGroup);
});
console.log('Form Array:', formArray);
}
createFormGroup(item: any): FormGroup {
const formGroup: { [key: string]: FormControl } = {};
Object.entries(item).forEach(([key, value]) => {
if (typeof value !== 'object') {
formGroup[key] = new FormControl(value ?? ''); // Ensure controls are created and enabled.
}
});
return this.fb.group(formGroup);
}
get billerDetails(): FormArray {
return this.billerForm.get('billerDetails') as FormArray;
}
getFormControlKeys(group: AbstractControl): string[] {
if (group instanceof FormGroup) {
return Object.keys(group.controls).filter(key =>
group.get(key) instanceof FormControl
);
}
return [];
}
getFieldChunks(keys: string[], size: number): string[][] {
const result = [];
for (let i = 0; i < keys.length; i += size) {
result.push(keys.slice(i, i + size));
}
return result;
}
}
View
<nb-tab tabTitle="Biller Details">
<div class="block-body">
<form [formGroup]="billerForm">
<div *ngFor="let billerGroup of billerDetails.controls; let i = index" [formGroup]="billerGroup">
<ng-container *ngIf="billerGroup?.controls">
<ng-container *ngFor="let chunk of getFieldChunks(getFormControlKeys(billerGroup), 3)">
<div class="row mb-4 justify-content-start">
<div class="col-md-4 d-flex flex-column" *ngFor="let key of chunk">
<label class="form-label">{{ key | titlecase }}</label>
<input nbInput class="w-100" [formControlName]="key" placeholder="Enter {{ key }}" fieldSize="large" />
</div>
</div>
</ng-container>
</ng-container>
</div>
</form>
</div>
</nb-tab>
Share
Improve this question
asked Mar 11 at 15:52
vishnuvishnu
4,59916 gold badges62 silver badges92 bronze badges
1 Answer
Reset to default 0Let me explain:
- I reduce the code to get better performance.
- I hope this help you out to see how it isn't the best way to create it, and why isn't working.
The main reason as you can see
with this form code is,
when 1 formControlName change
needs to recalculate the view again and blocking the UI for the user.
<form [formGroup]="billerForm">
<div formArrayName="billerDetails">
<div *ngFor="let billerGroup of billerDetails.controls; let i = index">
{{billerGroup.value | json}}
<div class="row mb-4 justify-content-start">
<ng-container [formGroupName]="i">
<div class="col-md-4 d-flex flex-column"
*ngFor="let input of billerGroup.value | keyvalue">
<label class="form-label">{{''+input.key|titlecase}}</label>
<input [formControlName]="''+input.key" />
</div>
</ng-container>
</div>
</div>
</div>
</form>
formArrayName
to access array form reference.billerGroup
is formControl so we have access to.value
[formGroupName]="i"
index of formArray.controls| keyvalue
angular pipe for get entrieskey,value
''+input.key
lifehack to give always a string.
Solution
Then I realize one thing, why angular needs to recalculate it!? and the reason is we're in a for loop and he don't know about the items!
- One important thing is when we do *ngFor we should use
trackBy
, newer version using @for is required. doc trackBy
stackblitz example
<div class="col-md-4 d-flex flex-column"
*ngFor="let input of billerGroup.value | keyvalue; trackBy: trackBy">
trackBy(index: number) {
return index;
}
now any item has reference and can update formControl!