What Is The Proper Way For Binding Data To Cascade Dropdown / Autocomplete Lists In Angular?
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 FormControl
s.
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?"