Although this post might be lengthy, my problem may not seem like a problem to someone more experienced than me. Still, I am writing it in the hope that it might help some of you.
I have divided this blog post into two parts. In the first part, I will discuss the coding problem I faced, making it easier for you to understand the solution. In the second part, I will explain how I solved my coding problem.
Firstly, the tech stack for the office project includes: Angular v17, .NET Core, Microsoft SQL, and the theme: PrimeNG v17.
The problem I’m discussing is related to retrieving saved data to update and set it in the entry form. In this case, part of the data needs to be looped through and set to the interface. Although the data was being set correctly, it was not displaying in the dropdown. Moreover, the data was not being modified correctly; even if it was, only the last element of the array was being updated.
I am not sharing the full code here due to privacy reasons. Instead, I have presented the main data as sample data to make it easier for you to understand.
JSON Data
{
"masterId": 1,
"masterDetails": [
{
"masterId": 1,
"detailsData":[
{
"detailsDataId": 123,
"subDetailsData":[
{
"subDetailsDataId": 133,
// here are other properties
},
{
"subDetailsDataId": 134,
// here are other properties
}
]
}
// here are other elements of detailsData table
]
}
]
}
We know that in Angular, there are primarily two types of forms for managing and handling user input:
- Template-Driven Forms
- Reactive Forms
In the project, we are using a Template-Driven Form.
Here is the code:
<div *ngFor="let d of detailsData; let i = index">
<p-button [label]="d.detailsDataId" icon="pi pi-plus" (click)="add(i)" />
<div *ngFor="let subDetailD of detailsData.subDetailsData; let c = index">
<div class="fromgrid grid my-1 align-items-center">
<div class="p-fluid col-12 md:col-6 xl:col-5 md:px-1 py-1">
<p-dropdown
styleClass="w-full"
class="w-full"
[options]="dropDownData[detailsData.detailsDataId]"
name="DetailsDataId"
(ngModel)]="subDetailD.subDetailsDataId"
optionValue="DetailsDataId"
optionLabel="DetailsDataName"
[filter]="true"
filterBy="DetailsDataName"
[showClear]="true"
placeholder="Select"
>
<ng-template pTemplate="selectedItem" let-selectedOption>
<div class="flex align-items-center gap-2">
<div>{{ selectedOption?.DetailsDataName }}</div>
</div>
</ng-template>
<ng-template let-data pTemplate="item">
<div class="flex align-items-center gap-2">
<div>{{ data.DetailsDataName }}</div>
</div>
</ng-template>
</p-dropdown>
</div>
<div class="p-fluid col-6 md:col-3 xl:col-1 md:px-1 py-1">
<p-button
icon="pi pi-times"
severity="danger"
[outlined]="true"
size="small"
(click)="remove(i, c)"
/>
</div>
</div>
</div>
</div>
I used ngModel
to set the data in the form. In this case, while looping through the data to set it in the interface, I encountered the issue. Despite making many changes to the code, no solution was found. Finally, I isolated the part of the code that was causing the problem as a child component.
Now, I am discussing the solution:
Parent Component (.html)
<div *ngFor="let d of detailsData; let i = index;">
<p-button [label]="d.detailsDataId" icon="pi pi-plus" (click)="add(i)" />
// Here is the child component which is looped based on the parent component
<app-child-component [data]="d" [parentIndex]="I"
(remove)="remove(i, $event)" />
</div>
Parent Component (.ts)
add(index: number): void { // Add a new child component dynamically
const newsubDetailD = {
name: `New Sub-Details
${this.detailsData[index].subDetailsData.length + 1}` };
this.detailsData[index].subDetailsData.push(newsubDetailD);
}
remove (index: number, childIndex: number): void {
// Method to remove a specific child component (e.g., by index)
this.detailsData[index].subDetailsData.splice(childIndex, 1);
}
Child Component (.html)
<div *ngIf="data">
<div *ngFor="let subDetailD of data.subDetailsData; let c = index">
<div class="fromgrid grid my-1 align-items-center">
<div class="p-fluid col-12 md:col-6 xl:col-5 md:px-1 py-1">
<p-dropdown
styleClass="w-full"
class="w-full"
[options]="dropDownData[data.detailsDataId]"
name="DetailsDataId"
(ngModel)]="subDetailD.subDetailsDataId"
optionValue="DetailsDataId"
optionLabel="DetailsDataName"
[filter]="true"
filterBy="DetailsDataName"
[showClear]="true"
placeholder="Select"
>
<ng-template pTemplate="selectedItem" let-selectedOption>
<div class="flex align-items-center gap-2">
<div>{{ selectedOption?.DetailsDataName }}</div>
</div>
</ng-template>
<ng-template let-data pTemplate="item">
<div class="flex align-items-center gap-2">
<div>{{ data.DetailsDataName }}</div>
</div>
</ng-template>
</p-dropdown>
</div>
<div class="p-fluid col-6 md:col-3 xl:col-1 md:px-1 py-1">
<p-button
icon="pi pi-times"
severity="danger"
[outlined]="true"
size="small"
(click)="remove(i, c)"
/>
</div>
<div class="p-fluid col-12 md:col-6 xl:col-1 md:px-1 py-1">
<p-button icon="pi pi-times" (click)="remove(c)" severity="danger" [outlined]="true" size="small" pTooltip="Remove Counter" tooltipPosition="bottom" />
</div>
</div>
</div>
</div>
_Child Component (.ts) _
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
export class childComponent implements OnInit {
constructor() {}
ngOnInit() {}
@Input() data: any; // Accept data from parent
@Input() parentIndex: number; // The index from the parent
@Output() remove: EventEmitter<number> = new EventEmitter<number>();
// Typed as number
remove(childIndex: number): void {
this.remove.emit(childIndex); // Emit the childIndex to the parent
}
}
By creating a child component, I successfully resolved the binding and looping issues that were previously causing trouble with ngModel. Sometimes, the solution lies in breaking things down into smaller, more manageable pieces. While this might seem like a simple fix to many, it was a valuable learning experience for me, and I hope it inspires others to approach similar challenges with curiosity and creativity. I'd love to hear your thoughts and insights—feel free to share them!
Top comments (0)