问题
I use mat-autocomplete
in several places in my project and fille them as shwon below on form loading:
countries: CountryDto[] = [];
cities: CityDto[] = [];
getCountries() {
this.demoService.getCountries().subscribe((list: CountryDto) => {
this.countries = list;
});
}
getCities(countryId) {
this.demoService.getCities(countryId).subscribe((list: CityDto) => {
this.cities= list;
});
}
However, when I tried to use a second list as cascade, I cannot make the second one to fill according to the first selection. Actually I pass the countryId and then retrieve the city list without any problem. But, I cannot refresh the list of cities. I also tried to use changeDetectorRef.detectChanges()
and ngZone.run()
, but it does not make any sense. I am not sure if the problem is related to subscribe
section, and for this reason I am too confused after trying lots of different tgings to fix the problem. So, first I need to start with a proper approach that is suitable for cascade binding. How can apply a proper way?
回答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 = new FormGroup({
state: new FormControl(),
city: new FormControl()
});
with the corresponding HTML
<div [formGroup]="citiesForm">
<select formControlName="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)
<select formControlName="state" >
<option value="">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.
回答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
来源:https://stackoverflow.com/questions/65195468/what-is-the-proper-way-for-binding-data-to-cascade-dropdown-autocomplete-lists