3
votes

I'm trying to create a dynamic form for users in the building industry, which will analyse inputs for a building (of any number of storeys) on a per-storey basis as such:

  1. User is initially presented with a form for a one-storey form, but given the option to add an extra storey:

  2. We should be able to add any number of extra storeys, and delete a specific storey if desired.

Method

To accomplish this, I'm trying to utilise *ngFor and iterate over an array which will take in the data, using ngModel to bind to each object in the array.

component.html

<form *ngFor = "let storey of storeyData; let i = index; trackBy: trackByFn(i)">
    <md-select placeholder="Floor type" name ="floorTypeSelector{{i}}" [(ngModel)]="storeyData[i].floorTypes[0]">
        <md-option *ngFor="let floorType of floorTypes" [value]="floorType.value">
            {{floorType.viewValue}}
        </md-option>
     </md-select>

<button md-raised-button (click)="incrementStoreyNumber()">
    <md-icon>library_add</md-icon>
     Add storey
</button> 

component.ts

export class FloorDetailsFormComponent implements OnInit {

selectedFloorType = [];
floorTypes = [
{value: 'concreteSlab', viewValue: 'Concrete slab'},
{value: 'suspendedTimber', viewValue: 'Suspended timber'},
{value: 'suspendedSlab', viewValue: 'Suspended slab'},
{value: 'wafflePod', viewValue: 'Waffle pod'}
]; 

storeyData = [{floorTypes: [],floorAreas:[] }];
storeyDataTemplate = {floorTypes: [], floorAreas:[]};

incrementStoreyNumber(){
    this.storeyData.push(this.storeyDataTemplate);
}

trackByFn(index){
 return index;
}
constructor() { }
ngOnInit() {
}

Problem

It seems that the first two storeys bind to their variables correctly, however changing the selected values of any of the 2nd to nth storeys will change the all other storeys (except the first).

After searching other posts about similar issues, I'm still at a loss as to why this is happening. One problem others had was that the name of the element wasn't being distinguished for each iteration of the *ngFor loop, however looking at my console.log, I can see the name of each element being indexed as it should.

One interesting thing I've seen, is that if I expand the storeyData array to a length of n storeys in the typescript file, all storeys up to n bind to their own independent variable as they should, and all storeys n+1 that are added later have the same problem.

I've tried using the trackBy feature, however I can't seem to get this to work either. I really lack understanding of what's going on under the hood when I'm trying to expand the *ngFor range on the fly. Perhaps this is just bad practice? If you could help me out here I'd be extremely grateful (even if its "hey, read up on this")

1
Check out my answer here, I think this is solution for your problem: stackoverflow.com/questions/41265761/… - Stefan Svrkota
Thanks very much for the reply Stefan. The background info on the formgroup was interesting. I switched to using [ngModelOptions]="{standalone: true}", and removing the name attribute as suggested in your linked solution, however it is still exhibiting the same behaviour. This is one of the suggested solutions I'd tried before without much success. - LNKX
Implement trackby on the second ngfor also. And change it for a argumentless version : trackBy: trackByFn and trackByFn(item, index){return index} - Vega
Thanks very much Vega. The behaviour would fit an instance where the select elements were not being distinguished from each other and hence the inputs being overwritten, so its a great idea. I haven't had time to implement your solution just yet, but I'll let you know how it goes in the next day or so :) - LNKX
Hi @Vega, I found that the trackBy function on the second ngFor loop didn't help in this case. To debug, after adding it, I tied the ngModel variable instead to an empty array, and pushed another empty array onto it each time the "Add Storey" button was clicked. Interestingly, all the data was saved correctly, and the select fields did not overwrite each other. I then removed both track by functions and observed the same behaviour :/ I feel that suggests there was some more fundamental problem with how I'm handling my data. This implementation isn't ideal, so I'll have another play around :) - LNKX

1 Answers

2
votes

The issue is in this line:

this.storeyData.push(this.storeyDataTemplate);

When you add storeyDataTemplate to storeyData, it's that same object who gets bound each time you do push, and ngFor tracks the same object. If you change to:

this.storeyData.push({floorTypes: [], floorAreas:[]});

it will work.

DEMO