Skip to content Skip to sidebar Skip to footer

What Is The Proper Way For Binding Data To Cascade Dropdown / Autocomplete Lists In Angular?

I use mat-autocomplete in several places in my project and fille them as shwon below on form loading: countries: CountryDto[] = []; cities: CityDto[] = []; getCountries() { thi

Solution 1:

You are mixing imperative and reactive programming by fetching the data and then assigning the parsed response to a variable. Angular can handle observable values just fine without ever having to subscribe to anything, by instead using the async pipe.

There's nothing wrong with using [(ngModel)] to bind values and to handle changes, but as a rule of thumb, when using Observables to populate the form based on selected values, I like to use only reactive programming with reactive forms instead of model-bindings.

A simple reactive form for you in this case could look something like

  citiesForm = newFormGroup({
    state: newFormControl(),
    city: newFormControl()
  });

with the corresponding HTML

<div [formGroup]="citiesForm"><selectformControlName="state" >
    .....

This makes it very easy to use the valueChanges event on either the FormGroup or it's individual FormControls.

To demonstrate with a working example, we can replace everything with observable values and a reactive form.

First, we declare our observables that will populate the form.

  states$: Observable<Array<any>>;
  cities$: Observable<Array<any>>;

We initiliaze the observables in the ngOnInit function (note the valueChanges).

  ngOnInit() {
    this.states$ = of(this.states);

    this.cities$ = combineLatest(
      this.citiesForm.get("state").valueChanges, // Will trigger when state changes
      of(this.cities)
    ).pipe(
      map(([selectedState, cities]) =>
        cities.filter(c => c.state === +selectedState) //SelectedState is a string
      )
    );
  }

and lastly bind everything to our form (note the async pipe and the form bindings to our reactive form)

<selectformControlName="state" ><optionvalue="">Select State</option><option *ngFor="let s of states$ | async" [value]="s.id">{{s.name}}</option></select>

Since you mentioned manual triggering of change detection using ChangeDetectorRef in your question, notice that my proposed answer uses the OnPush change detection strategy, and there's no need to think about change detection since the async pipe will know that emission of new values in the observables will affect the template and render it properly.

Solution 2:

Simplifly the Daniel's idea, you can defined a observable like

cities$=this.citiesForm.get('state').valueChange.pipe(
  switchMap(countryId=>this.demoService.getCities(countryId),
  tap(_=>this.citiesForm.get('city').setValue(null)
)

And use

<options *ngFor="let s of cities$ | async">

Usig ngModel is equal

<select [ngModel]="state" (ngModelChange)="stateChange($event)" >
 ...
</select>


stateChange(countryId)
{
  this.state=state;
  this.cities$=this.demoService.getCities(countryId).pipe(
      tap(_=>this.city=null
  )
}

See that the idea is return an observable and use pipe async in the .html. The operator "tap" make that, when you subscribe -remember that the pipe async is a way to subscribe- the city becomes null

Post a Comment for "What Is The Proper Way For Binding Data To Cascade Dropdown / Autocomplete Lists In Angular?"