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);
|
fn(selected);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -765,12 +765,23 @@ export function main() {
|
||||||
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
comp.cities = [{'name': 'SF'}, {'name': 'NYC'}, {'name': 'Buffalo'}];
|
||||||
});
|
});
|
||||||
|
|
||||||
const setSelectedCities = (selectedCities: any): void => {
|
const detectChangesAndTick = (): void => {
|
||||||
comp.selectedCities = selectedCities;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
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 assertOptionElementSelectedState = (selectedStates: boolean[]): void => {
|
||||||
const options = fixture.debugElement.queryAll(By.css('option'));
|
const options = fixture.debugElement.queryAll(By.css('option'));
|
||||||
if (options.length !== selectedStates.length) {
|
if (options.length !== selectedStates.length) {
|
||||||
|
@ -781,36 +792,30 @@ export function main() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const testNewModelValueUnselectsAllOptions = (modelValue: any): void => {
|
it('should reflect state of model after option selected and new options subsequently added',
|
||||||
setSelectedCities([comp.cities[1]]);
|
fakeAsync(() => {
|
||||||
assertOptionElementSelectedState([false, true, false]);
|
setSelectedCities([]);
|
||||||
|
|
||||||
setSelectedCities(modelValue);
|
selectOptionViaUI('1: Object');
|
||||||
|
|
||||||
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);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should clear all selected option elements when value of wrong type supplied',
|
|
||||||
fakeAsync(() => { testNewModelValueUnselectsAllOptions(''); }));
|
|
||||||
|
|
||||||
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]]);
|
|
||||||
assertOptionElementSelectedState([false, true, false]);
|
assertOptionElementSelectedState([false, true, false]);
|
||||||
|
|
||||||
|
comp.cities.push({'name': 'Chicago'});
|
||||||
|
detectChangesAndTick();
|
||||||
|
|
||||||
|
assertOptionElementSelectedState([false, true, false, false]);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should reflect state of model after option selected and then other options removed',
|
||||||
|
fakeAsync(() => {
|
||||||
|
setSelectedCities([]);
|
||||||
|
|
||||||
|
selectOptionViaUI('1: Object');
|
||||||
|
assertOptionElementSelectedState([false, true, false]);
|
||||||
|
|
||||||
|
comp.cities.pop();
|
||||||
|
detectChangesAndTick();
|
||||||
|
|
||||||
|
assertOptionElementSelectedState([false, true]);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue