angulartypescriptdrop-down-menucascadingdropdownmat-select

Angular cascading mat-select dropdowns


I am trying to make a mat-select cascading dropdown list. The problem is that when I click on the "Country" dropdown and select an option, it does not appear as selected so I can't even test the whole cascading dropdown.

dependent-dropdown-component.html:

<div class="content">
<mat-form-field appearance="outline">
    <mat-label>Country</mat-label>
    <mat-select [(ngModel)]="selectedCountry" (change)="changeCountry($event)">
        <mat-option *ngFor="let country of Countries">{{country.name}}</mat-option>
    </mat-select>
</mat-form-field>

<mat-form-field appearance="outline">
    <mat-label>State</mat-label>
    <mat-select (change)="changeState($event)">
        <mat-option *ngFor="let state of states">{{state.name}}</mat-option>
    </mat-select>
</mat-form-field>

<mat-form-field appearance="outline">
    <mat-label>City</mat-label>
    <mat-select>
        <mat-option *ngFor="let city of cities">{{city}}</mat-option>
    </mat-select>
</mat-form-field>

dependent-dropdown-component.ts:

import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';

@Component({
  selector: 'app-dependent-dropdown',
  templateUrl: './dependent-dropdown.component.html',
  styleUrls: ['./dependent-dropdown.component.css']
})

export class DependentDropdownComponent implements OnInit {
  constructor(private title: Title) { }

  ngOnInit() { }

  selectedCountry: String = "ChooseCountry";

  Countries: Array<any> = [
    {
      name: 'Romania',
      states: [
        {
          name: 'Cluj',
          cities: ['Cluj-Napoca', 'Dej', 'Gherla']
        },
        {
          name: 'Sibiu',
          cities: ['Sibiu', 'Avrig', 'Cisnadie']
        },
        {
          name: 'Satu Mare',
          cities: ['Baia-Mare', 'Satu-Mare', 'Carei']
        },
      ]
    },

    {
      name: 'United States',
      states: [
        {
          name: 'Washington',
          cities: ['Seattle', 'Vancouver', 'Kent']
        },
        {
          name: 'Texas',
          cities: ['Houston', 'El Paso', 'Dallas']
        },
        {
          name: 'California',
          cities: ['Los Angeles', 'San Francisco', 'San Diego']
        },
      ]
    },
  ];

  states: Array<any> = [];
  cities: Array<any> = [];

  changeCountry(country: any) {
    this.states = this.Countries.find((cntry: any) => cntry.name == country.target.value).states;
  }

  changeState(state: any) {
    this.cities = this.Countries.find((cntry: any) => cntry.name == this.selectedCountry).states.find((stat: any) => stat.name == state.target.value).cities;
  }
}

source: https://roytuts.com/cascading-or-dependent-dropdown-using-angular/


Solution

  • The first issue I notice here is that you don't have a value attribute on the mat-options. This is why the selected country does not stay selected. To solve that, add the [value]="country" binding on the mat-option (same goes for the other dropdowns as well). Secondly, there does not seem to be any change event emitted by the mat-select. Looking at the docs I supposed you are looking for the valueChange event instead.

    <div class="content">
      <mat-form-field appearance="outline">
        <mat-label>Country</mat-label>
        <mat-select [(ngModel)]="selectedCountry" (valueChange)="changeCountry($event)">
          <mat-option *ngFor="let country of Countries" [value]="country">{{
            country.name
          }}</mat-option>
        </mat-select>
      </mat-form-field>
    
      <mat-form-field appearance="outline">
        <mat-label>State</mat-label>
        <mat-select (valueChange)="changeState($event)">
          <mat-option *ngFor="let state of states" [value]="state">{{ state.name }}</mat-option>
        </mat-select>
      </mat-form-field>
    
      <mat-form-field appearance="outline">
        <mat-label>City</mat-label>
        <mat-select>
          <mat-option *ngFor="let city of cities" [value]="city">{{ city }}</mat-option>
        </mat-select>
      </mat-form-field>
    </div>
    

    The event handlers can also be a bit simpler:

    changeCountry(country: any) {
      this.states = country.states;
      this.cities = [];
    }
    
    changeState(state: any) {
      this.cities = state.cities;
    }