Skip to content

Commit 4903636

Browse files
committed
fix(select): value update not being propagated when selected option is removed or added
Fixes the select not propagating its value back up to the value accessor if a selected option is added or removed. Previously this only happened the next time the user interacted with an option. Fixes #9038.
1 parent 5414480 commit 4903636

File tree

2 files changed

+106
-3
lines changed

2 files changed

+106
-3
lines changed

src/lib/select/select.spec.ts

+81-1
Original file line numberDiff line numberDiff line change
@@ -3459,6 +3459,84 @@ describe('MatSelect', () => {
34593459
}));
34603460

34613461
});
3462+
3463+
describe('value propagation when options are added/removed', () => {
3464+
let fixture: ComponentFixture<BasicSelectWithoutFormsMultiple>;
3465+
let testComponent: BasicSelectWithoutFormsMultiple;
3466+
3467+
beforeEach(fakeAsync(() => {
3468+
configureMatSelectTestingModule([BasicSelectWithoutFormsMultiple]);
3469+
fixture = TestBed.createComponent(BasicSelectWithoutFormsMultiple);
3470+
testComponent = fixture.componentInstance;
3471+
fixture.detectChanges();
3472+
}));
3473+
3474+
it('should propagate the changes when a selected option is removed', fakeAsync(() => {
3475+
testComponent.selectedFoods = ['steak-0', 'pizza-1'];
3476+
fixture.detectChanges();
3477+
flush();
3478+
3479+
expect(testComponent.select.value).toEqual(['steak-0', 'pizza-1']);
3480+
testComponent.selectionChange.calls.reset();
3481+
3482+
testComponent.foods.shift();
3483+
fixture.detectChanges();
3484+
flush();
3485+
3486+
expect(testComponent.selectionChange).toHaveBeenCalledTimes(1);
3487+
expect(testComponent.select.value).toEqual(['pizza-1']);
3488+
expect(testComponent.selectedFoods).toEqual(['pizza-1']);
3489+
}));
3490+
3491+
it('should propagate the changes when a selected option is added', fakeAsync(() => {
3492+
testComponent.selectedFoods = ['steak-0'];
3493+
fixture.detectChanges();
3494+
flush();
3495+
3496+
expect(testComponent.select.value).toEqual(['steak-0']);
3497+
testComponent.selectionChange.calls.reset();
3498+
3499+
testComponent.foods.push({value: 'pasta-4', viewValue: 'Pasta'});
3500+
testComponent.selectedFoods.push('pasta-4');
3501+
fixture.detectChanges();
3502+
flush();
3503+
3504+
expect(testComponent.selectionChange).toHaveBeenCalledTimes(1);
3505+
expect(testComponent.select.value).toEqual(['steak-0', 'pasta-4']);
3506+
expect(testComponent.selectedFoods).toEqual(['steak-0', 'pasta-4']);
3507+
}));
3508+
3509+
it('should not propagate changes when a non-selected option is removed', fakeAsync(() => {
3510+
testComponent.selectedFoods = ['steak-0'];
3511+
fixture.detectChanges();
3512+
flush();
3513+
3514+
testComponent.selectionChange.calls.reset();
3515+
testComponent.foods.pop();
3516+
fixture.detectChanges();
3517+
flush();
3518+
3519+
expect(testComponent.selectionChange).not.toHaveBeenCalled();
3520+
expect(testComponent.select.value).toEqual(['steak-0']);
3521+
expect(testComponent.selectedFoods).toEqual(['steak-0']);
3522+
}));
3523+
3524+
it('should not propagate changes when a non-selected option is added', fakeAsync(() => {
3525+
testComponent.selectedFoods = ['steak-0'];
3526+
fixture.detectChanges();
3527+
flush();
3528+
3529+
testComponent.selectionChange.calls.reset();
3530+
testComponent.foods.push({value: 'pasta-4', viewValue: 'Pasta'});
3531+
fixture.detectChanges();
3532+
flush();
3533+
3534+
expect(testComponent.selectionChange).not.toHaveBeenCalled();
3535+
expect(testComponent.select.value).toEqual(['steak-0']);
3536+
expect(testComponent.selectedFoods).toEqual(['steak-0']);
3537+
}));
3538+
3539+
});
34623540
});
34633541

34643542

@@ -4021,7 +4099,8 @@ class BasicSelectWithoutFormsPreselected {
40214099
@Component({
40224100
template: `
40234101
<mat-form-field>
4024-
<mat-select placeholder="Food" [(value)]="selectedFoods" multiple>
4102+
<mat-select multiple placeholder="Food" [(value)]="selectedFoods"
4103+
(selectionChange)="selectionChange()">
40254104
<mat-option *ngFor="let food of foods" [value]="food.value">
40264105
{{ food.viewValue }}
40274106
</mat-option>
@@ -4031,6 +4110,7 @@ class BasicSelectWithoutFormsPreselected {
40314110
})
40324111
class BasicSelectWithoutFormsMultiple {
40334112
selectedFoods: string[];
4113+
selectionChange = jasmine.createSpy('selectionChange spy');
40344114
foods: any[] = [
40354115
{ value: 'steak-0', viewValue: 'Steak' },
40364116
{ value: 'pizza-1', viewValue: 'Pizza' },

src/lib/select/select.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,9 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
486486
ngAfterContentInit() {
487487
this._initKeyManager();
488488

489-
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(() => {
489+
this.options.changes.pipe(startWith(null), takeUntil(this._destroy)).subscribe(options => {
490490
this._resetOptions();
491-
this._initializeSelection();
491+
options ? this._handleSelectionChange() : this._initializeSelection();
492492
});
493493
}
494494

@@ -743,6 +743,29 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
743743
});
744744
}
745745

746+
/**
747+
* Handles changes in the amount of options. Sets the `selected` status of any newly-added
748+
* options and propagates the changes back up, if any of the selected options were removed
749+
* or any selected options were added.
750+
*/
751+
private _handleSelectionChange(): void {
752+
Promise.resolve().then(() => {
753+
// Save the currently-selected options for reference.
754+
const previousSelection = this._selectionModel.selected;
755+
756+
// Update the selected options.
757+
this._setSelectionByValue(this.ngControl ? this.ngControl.value : this._value);
758+
759+
const currentSelection = this._selectionModel.selected;
760+
761+
// Check if the selection has changed and propagate the changes to the model.
762+
if (previousSelection.length !== currentSelection.length ||
763+
currentSelection.find(option => previousSelection.indexOf(option) === -1)) {
764+
this._propagateChanges();
765+
}
766+
});
767+
}
768+
746769
/**
747770
* Sets the selected option based on a value. If no option can be
748771
* found with the designated value, the select trigger is cleared.

0 commit comments

Comments
 (0)