Search code examples
angularfrontendhtml-selectangular-ngmodel

Trouble with binding data using ngModel between 3 select option dropdowns. (Angular)


Something that I'm working on has 3 selections and I'm trying to have them synchronize/bind together nicely. I've made some dummy code that summarizes my issue (since I have a larger project and make a bunch of API calls to get the data into this form). Here it is:

populate-dropdown.component.html

<div id=myForm>
  <label> Brands:
    <select id ="Select1" class="input-small" [(ngModel)]="currentBrand">
      <option *ngFor="let brand of getListOfBrands()">
        {{ brand }}
      </option>
    </select>
  </label>
</div>
<br/>

<div id=myForm>
  <label> Models:
    <select id ="Select1" class="input-small" [(ngModel)]="currentModel">
      <option *ngFor="let model of getListOfModels(currentBrand)">
        {{ model }}
      </option>
    </select>
  </label>
</div>
<br/>

<div id=myForm>
  <label> Model Numbers:
    <select id ="Select1" class="input-small" [(ngModel)]="currentNumber">
      <option *ngFor="let number of getListOfNumbers(currentModel)">
        {{ number }}
      </option>
    </select>
  </label>
</div>

populate-dropdown.component.ts

import { Component, OnInit, Output } from '@angular/core';
import { DropdownData } from '../dropdown-data';

@Component({
  selector: 'app-populate-dropdown',
  templateUrl: './populate-dropdown.component.html',
  styleUrls: ['./populate-dropdown.component.css']
})
export class PopulateDropdownComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    this.initializeStuff();
  }

  currentBrand: string;
  currentModel: string;
  currentNumber: number;

  listOfDropdownData = [];

  private initializeStuff() {
    let firstGroup = new DropdownData();
    let secondGroup = new DropdownData();

    firstGroup.nameOfBrand = "The Best";
    secondGroup.nameOfBrand = "The Worst";

    firstGroup.listOfModels = ["Best1", "Best2", "Best3", "Best4", "Best5"];
    secondGroup.listOfModels = ["Worst1", "Worst2"];

    let one = {model: "Best1", modelNumbers: [1901, 1905, 1909]};
    let two = {model: "Best2", modelNumbers: [200, 2000, 20000, 20000]};
    let three = {model: "Best3", modelNumbers: [300]};
    let four = {model: "Best4", modelNumbers: [400, 4500]};
    let five = {model: "Best5", modelNumbers: [510, 520, 530]};

    firstGroup.listOfModelsWithNumbers.push(one);
    firstGroup.listOfModelsWithNumbers.push(two);
    firstGroup.listOfModelsWithNumbers.push(three);
    firstGroup.listOfModelsWithNumbers.push(four);
    firstGroup.listOfModelsWithNumbers.push(five);

    let first = {model: "Worst1", modelNumbers: [97, 24, 108]};
    let second = {model: "Worst2", modelNumbers: [19]};
    secondGroup.listOfModelsWithNumbers.push(first);
    secondGroup.listOfModelsWithNumbers.push(second);

    this.listOfDropdownData.push(firstGroup);
    this.listOfDropdownData.push(secondGroup);
  }

  private getListOfBrands() {
    let i : number;
    let myList = [];
    for(i = 0; i < this.listOfDropdownData.length; i++) {
      myList.push(this.listOfDropdownData[i].nameOfBrand);
    }
    return myList;
  }

  private getListOfModels(brand: string) {
    let i : number;
    for(i = 0; i < this.listOfDropdownData.length; i++) {
      if(this.listOfDropdownData[i].nameOfBrand == brand) {
        return this.listOfDropdownData[i].listOfModels;
      }
    }
  }

  private getListOfNumbers(model: string) {
    let i: number;
    let j: number;
    for(i = 0; i < this.listOfDropdownData.length; i++) {
      for(j = 0; j < this.listOfDropdownData[i].listOfModelsWithNumbers.length; j++) {
        if(this.listOfDropdownData[i].listOfModelsWithNumbers[j].model == model) {
          return this.listOfDropdownData[i].listOfModelsWithNumbers[j].modelNumbers;
        }
      }
    }
  }
}

dropdown-data.ts

export class DropdownData {
    nameOfBrand: string;
    listOfModels: string[];
    listOfModelsWithNumbers: any[] = [];
}

And I simply just call my populateDropdown component in my appComponent like this:

<app-populate-dropdown></app-populate-dropdown>

So to understand what I'm trying to achieve: I want the page to initially default with the first brand, the first model that shows up for it, and the first number associated with that model. If the user changes the model, the modelNumbers should change as well, and if the user changes the brand, I want to show once again the first model and its list of modelNumbers.

To put it simply: Each brand has its own list of models which has its own list of modelNumbers

My first issue is that after I use ngModel, it seems as though I'm unable to set default values (I want it to display my first option for all of them), and so I was wondering how I could approach this.

My next issue is that although the brand and models are synchronized, the modelNumbers will not show unless I change the model (I want them to default) and when I switch brands, the modelNumbers from the previous brand's model are still there.

What can I do to fix this without working too much around the structure of what I've got? Also for future reference, what would be the best way for such kind of communication between data? Thanks


Solution

  • There are some tips:

    Use (change) in select

    Use the change event and update the lists on change. like this:

    <select id ="Select1" class="input-small" [(ngModel)]="currentBrand" (change)="onBrandChange()">
      <option *ngFor="let brand of brandsList">
        {{ brand }}
      </option>
    </select>
    

    Don't push to listOfDropdownData instead reinitialize it

    Change this code:

    this.listOfDropdownData.push(firstGroup);
    this.listOfDropdownData.push(secondGroup);
    

    to this:

    this.listOfDropdownData = [...this.listOfDropdownData, firstGroup , secondGroup];