fix(forms): ensure select[multiple] retains selections
If you bound an array to select[multiple] via ngModel and subsequently changed the options to select from, the UI would drop any selections made since by the user. This was due to SelectMultipleControlValueAccessor not keeping a reference to the new model arrays it generated when users interacted with the select control. Update code to keep the reference. Closes #12527 Closes #12654
This commit is contained in:
parent
2bf1bbc071
commit
821b8f09d6
|
@ -99,6 +99,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
|||
}
|
||||
}
|
||||
}
|
||||
this.value = selected;
|
||||
fn(selected);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -765,12 +765,23 @@ export function main() {
|
|||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
||||
});
|
||||
|
||||
const setSelectedCities = (selectedCities: any): void => {
|
||||
comp.selectedCities = selectedCities;
|
||||
const detectChangesAndTick = (): void => {
|
||||
fixture.detectChanges();
|
||||
tick();
|
||||
};
|
||||
|
||||
const setSelectedCities = (selectedCities: any): void => {
|
||||
comp.selectedCities = selectedCities;
|
||||
detectChangesAndTick();
|
||||
};
|
||||
|
||||
const selectOptionViaUI = (valueString: string): void => {
|
||||
const select = fixture.debugElement.query(By.css('select'));
|
||||
select.nativeElement.value = valueString;
|
||||
dispatchEvent(select.nativeElement, 'change');
|
||||
detectChangesAndTick();
|
||||
};
|
||||
|
||||
const assertOptionElementSelectedState = (selectedStates: boolean[]): void => {
|
||||
const options = fixture.debugElement.queryAll(By.css('option'));
|
||||
if (options.length !== selectedStates.length) {
|
||||
|
@ -781,36 +792,30 @@ export function main() {
|
|||
}
|
||||
};
|
||||
|
||||
const testNewModelValueUnselectsAllOptions = (modelValue: any): void => {
|
||||
setSelectedCities([comp.cities[1]]);
|
||||
it('should reflect state of model after option selected and new options subsequently added',
|
||||
fakeAsync(() => {
|
||||
setSelectedCities([]);
|
||||
|
||||
selectOptionViaUI('1: Object');
|
||||
assertOptionElementSelectedState([false, true, false]);
|
||||
|
||||
setSelectedCities(modelValue);
|
||||
comp.cities.push({'name': 'Chicago'});
|
||||
detectChangesAndTick();
|
||||
|
||||
const select = fixture.debugElement.query(By.css('select'));
|
||||
expect(select.nativeElement.value).toEqual('');
|
||||
assertOptionElementSelectedState([false, false, false]);
|
||||
};
|
||||
|
||||
it('should support setting value to null and undefined', fakeAsync(() => {
|
||||
testNewModelValueUnselectsAllOptions(null);
|
||||
testNewModelValueUnselectsAllOptions(undefined);
|
||||
assertOptionElementSelectedState([false, true, false, false]);
|
||||
}));
|
||||
|
||||
it('should clear all selected option elements when value of wrong type supplied',
|
||||
fakeAsync(() => { testNewModelValueUnselectsAllOptions(''); }));
|
||||
it('should reflect state of model after option selected and then other options removed',
|
||||
fakeAsync(() => {
|
||||
setSelectedCities([]);
|
||||
|
||||
it('should set option elements to selected that are present in model', fakeAsync(() => {
|
||||
setSelectedCities([comp.cities[0], comp.cities[2]]);
|
||||
assertOptionElementSelectedState([true, false, true]);
|
||||
}));
|
||||
|
||||
it('should clear selected option elements since removed from model', fakeAsync(() => {
|
||||
const selectedCities: [{'name:string': string}] = <[{'name:string': string}]>[];
|
||||
selectedCities.push.apply(selectedCities, comp.cities);
|
||||
setSelectedCities(selectedCities);
|
||||
setSelectedCities([comp.cities[1]]);
|
||||
selectOptionViaUI('1: Object');
|
||||
assertOptionElementSelectedState([false, true, false]);
|
||||
|
||||
comp.cities.pop();
|
||||
detectChangesAndTick();
|
||||
|
||||
assertOptionElementSelectedState([false, true]);
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue