Skip to main content

Overview

The Dropdown component provides a Material Design dropdown for selecting one or multiple options from a list. It supports static options, promise-based loading, and observable-based dynamic data.

When to Use

Use the Dropdown component when you need to:
  • Let users select from a predefined list of options
  • Load options from an API or async data source
  • Enable multi-select for choosing multiple values
  • Present a large list of choices in a compact space
For searchable dropdowns, consider using AutoComplete. For small sets of mutually exclusive options, use Radio Group.

Basic Usage

import { Dropdown, OptionChild } from 'mat-dynamic-form';

const countryDropdown = new Dropdown(
  'country',
  'Country',
  [
    new OptionChild('United States', 'us'),
    new OptionChild('United Kingdom', 'uk'),
    new OptionChild('Canada', 'ca')
  ]
);

Common Patterns

const countryDropdown = new Dropdown(
  'country',
  'Select Country',
  [
    new OptionChild('United States', 'us'),
    new OptionChild('United Kingdom', 'uk'),
    new OptionChild('Canada', 'ca'),
    new OptionChild('Australia', 'au')
  ],
  'us' // Pre-select United States
).apply({
  icon: 'public',
  validator: Validators.required
});

Multi-Select Dropdown

import { Validators } from '@angular/forms';

const languagesDropdown = new Dropdown(
  'languages',
  'Languages',
  [
    new OptionChild('English', 'en'),
    new OptionChild('Spanish', 'es'),
    new OptionChild('French', 'fr'),
    new OptionChild('German', 'de'),
    new OptionChild('Chinese', 'zh')
  ],
  null,
  true // Enable multi-select
).apply({
  icon: 'language',
  hint: 'Select all languages you speak'
});

Loading Options from Promise

const cityDropdown = new Dropdown(
  'city',
  'City',
  fetch('/api/cities').then(response => response.json())
).apply({
  icon: 'location_city',
  validator: Validators.required
});

Loading Options from Observable

import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

// In your component:
constructor(private http: HttpClient) {
  const countryDropdown = new Dropdown(
    'country',
    'Country',
    this.http.get<any[]>('https://restcountries.com/v3.1/all').pipe(
      map(countries => 
        countries.map(c => new OptionChild(c.name.common, c.cca2))
      )
    )
  ).apply({
    icon: 'public',
    validator: Validators.required
  });
}
const categoryDropdown = new Dropdown(
  'category',
  'Category',
  [
    new OptionChild('Electronics', 'electronics'),
    new OptionChild('Clothing', 'clothing'),
    new OptionChild('Books', 'books')
  ]
).apply({
  action: {
    type: 'valueChange',
    onEvent: (param) => {
      console.log('Selected category:', param.event);
      // Load subcategories based on selection
      this.loadSubcategories(param.event);
    }
  }
});

Properties

id
string
required
Unique identifier for the dropdown.
placeholder
string
Placeholder text shown when no option is selected.
value
OptionChild[] | Promise<OptionChild[]> | Observable<OptionChild[]>
required
The list of options to display. Can be static array, Promise, or Observable.
selectedValue
string
The initially selected value (must match an option’s value).
multiple
boolean
default:"false"
Whether to allow selecting multiple options.
icon
string
Material icon name to display alongside the dropdown.
validator
ValidatorFn | ValidatorFn[]
Angular validators to apply.
errorMessage
string
Custom error message shown when validation fails.
hint
string
Helper text displayed below the dropdown.
disabled
boolean
default:"false"
Whether the dropdown is disabled.
singleLine
boolean
default:"false"
Whether the dropdown takes up a full row in the form grid.

Methods

getOptions()

Returns the current list of options.
const dropdown = formStructure.getNodeById<Dropdown>('country');
const options = dropdown.getOptions();
console.log(options); // Array of OptionChild

apply()

Applies multiple properties at once.
dropdown.apply({
  icon: 'public',
  validator: Validators.required,
  hint: 'Select your country'
});

OptionChild Structure

Options are defined using the OptionChild class:
import { OptionChild } from 'mat-dynamic-form';

const option = new OptionChild(
  'Display Text', // title - shown to user
  'value'        // value - stored in form data
);

Example with Descriptive Options

const priorityDropdown = new Dropdown(
  'priority',
  'Priority Level',
  [
    new OptionChild('Low - Can wait', 'low'),
    new OptionChild('Medium - Normal timeline', 'medium'),
    new OptionChild('High - Urgent', 'high'),
    new OptionChild('Critical - Immediate action', 'critical')
  ]
);

Validation Examples

Required Selection

import { Validators } from '@angular/forms';

const dropdown = new Dropdown('country', 'Country', options).apply({
  validator: Validators.required,
  errorMessage: 'Please select a country'
});

Custom Validator

import { AbstractControl, ValidationErrors } from '@angular/forms';

function notUSValidator(control: AbstractControl): ValidationErrors | null {
  if (control.value === 'us') {
    return { notAllowed: true };
  }
  return null;
}

const dropdown = new Dropdown('country', 'Country', options).apply({
  validator: [Validators.required, notUSValidator],
  errorMessage: 'US is not available for this service'
});

Dynamic Options Example

Cascading Dropdowns

import { Component } from '@angular/core';
import { FormStructure, Dropdown, OptionChild } from 'mat-dynamic-form';

@Component({
  selector: 'app-location-form',
  template: '<mat-dynamic-form [structure]="formStructure"></mat-dynamic-form>'
})
export class LocationFormComponent {
  formStructure: FormStructure;
  
  constructor() {
    this.formStructure = new FormStructure('Select Location');
    this.formStructure.nodeGrid = 2;
    
    this.formStructure.nodes = [
      new Dropdown(
        'country',
        'Country',
        [
          new OptionChild('United States', 'us'),
          new OptionChild('Canada', 'ca'),
          new OptionChild('Mexico', 'mx')
        ]
      ).apply({
        validator: Validators.required,
        action: {
          type: 'valueChange',
          onEvent: (param) => this.onCountryChange(param.event)
        }
      }),
      
      new Dropdown('state', 'State/Province', []).apply({
        validator: Validators.required,
        disabled: true // Initially disabled
      })
    ];
  }
  
  onCountryChange(countryCode: string) {
    const stateDropdown = this.formStructure.getNodeById<Dropdown>('state');
    
    if (!stateDropdown) return;
    
    // Load states based on country
    const states = this.getStatesForCountry(countryCode);
    stateDropdown.value = states;
    stateDropdown.disabled = false;
    
    // Reset the state selection
    this.formStructure.getControlById('state')?.setValue(null);
  }
  
  getStatesForCountry(countryCode: string): OptionChild[] {
    const statesMap: Record<string, OptionChild[]> = {
      'us': [
        new OptionChild('California', 'ca'),
        new OptionChild('New York', 'ny'),
        new OptionChild('Texas', 'tx')
      ],
      'ca': [
        new OptionChild('Ontario', 'on'),
        new OptionChild('Quebec', 'qc'),
        new OptionChild('British Columbia', 'bc')
      ],
      'mx': [
        new OptionChild('Mexico City', 'cdmx'),
        new OptionChild('Jalisco', 'jal'),
        new OptionChild('Nuevo León', 'nl')
      ]
    };
    
    return statesMap[countryCode] || [];
  }
}

Best Practices

Use descriptive option titles - Make it clear what each option represents. Users should understand the choice without additional context.
Keep option lists manageable - For lists with more than 10-15 items, consider using AutoComplete for better user experience.
Handle loading states - When loading options from an API, the dropdown will be empty until data arrives. Consider showing a loading indicator or placeholder.
Use multi-select sparingly - Multi-select dropdowns are harder to use on mobile. If users typically select many options, consider checkboxes instead.
Pre-select sensible defaults - If one option is most common, pre-select it to save users time:
new Dropdown('country', 'Country', options, 'us')

Complete Example

import { Component } from '@angular/core';
import { Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { FormStructure, Dropdown, OptionChild, Button } from 'mat-dynamic-form';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-registration-form',
  template: '<mat-dynamic-form [structure]="formStructure"></mat-dynamic-form>'
})
export class RegistrationFormComponent {
  formStructure: FormStructure;
  
  constructor(private http: HttpClient) {
    this.formStructure = new FormStructure('User Registration');
    this.formStructure.appearance = 'outline';
    this.formStructure.nodeGrid = 2;
    
    this.formStructure.nodes = [
      // Static options
      new Dropdown(
        'role',
        'Role',
        [
          new OptionChild('Developer', 'developer'),
          new OptionChild('Designer', 'designer'),
          new OptionChild('Manager', 'manager'),
          new OptionChild('Other', 'other')
        ]
      ).apply({
        icon: 'work',
        validator: Validators.required,
        singleLine: false
      }),
      
      // Dynamic options from API
      new Dropdown(
        'country',
        'Country',
        this.http.get<any[]>('https://restcountries.com/v3.1/all').pipe(
          map(countries => 
            countries
              .map(c => new OptionChild(c.name.common, c.cca2))
              .sort((a, b) => a.title.localeCompare(b.title))
          )
        )
      ).apply({
        icon: 'public',
        validator: Validators.required,
        singleLine: false
      }),
      
      // Multi-select
      new Dropdown(
        'skills',
        'Skills',
        [
          new OptionChild('JavaScript', 'js'),
          new OptionChild('TypeScript', 'ts'),
          new OptionChild('Python', 'python'),
          new OptionChild('Java', 'java'),
          new OptionChild('C#', 'csharp'),
          new OptionChild('PHP', 'php')
        ],
        null,
        true // multi-select
      ).apply({
        icon: 'code',
        hint: 'Select all that apply',
        singleLine: true
      })
    ];
    
    this.formStructure.validateActions = [
      new Button('submit', 'Submit', {
        style: 'primary',
        onEvent: (param) => this.onSubmit(param.structure)
      }).apply({
        validateForm: true,
        icon: 'check'
      })
    ];
  }
  
  onSubmit(structure: FormStructure) {
    const data = structure.getValue();
    console.log('Form data:', data);
    // data.skills will be an array of selected values
  }
}

Radio Group

For 3-5 mutually exclusive options

AutoComplete

Searchable dropdown with filtering

Checkbox

For simple yes/no or on/off toggles

See Also

Build docs developers (and LLMs) love