Skip to content

Commit 5f565b6

Browse files
committed
fix(material/select): value not updated if the same array is updated and re-assigned
`mat-select` has an internal check that doesn't re-assign a value if the reference is the same. This was meant primarily for primitives, but it ends up breaking the behavior for a multi-select where the value is always an array. These changes skip the check for a multi-select receiving a new array value. Fixes #21583.
1 parent 0911820 commit 5f565b6

File tree

3 files changed

+48
-1
lines changed

3 files changed

+48
-1
lines changed

src/material-experimental/mdc-select/select.spec.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3742,6 +3742,30 @@ describe('MDC-based MatSelect', () => {
37423742
}).not.toThrow();
37433743
}));
37443744

3745+
it('should update the option selected state if the same array is mutated and passed back in',
3746+
fakeAsync(() => {
3747+
const value: string[] = [];
3748+
trigger.click();
3749+
testInstance.control.setValue(value);
3750+
fixture.detectChanges();
3751+
3752+
const optionNodes =
3753+
Array.from<HTMLElement>(overlayContainerElement.querySelectorAll('mat-option'));
3754+
const optionInstances = testInstance.options.toArray();
3755+
3756+
expect(optionNodes.some(option => {
3757+
return option.classList.contains('mdc-list-item--selected');
3758+
})).toBe(false);
3759+
expect(optionInstances.some(option => option.selected)).toBe(false);
3760+
3761+
value.push('eggs-5');
3762+
testInstance.control.setValue(value);
3763+
fixture.detectChanges();
3764+
3765+
expect(optionNodes[5].classList).toContain('mdc-list-item--selected');
3766+
expect(optionInstances[5].selected).toBe(true);
3767+
}));
3768+
37453769
});
37463770

37473771
it('should be able to provide default values through an injection token', fakeAsync(() => {

src/material/select/select.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4621,6 +4621,28 @@ describe('MatSelect', () => {
46214621
}).not.toThrow();
46224622
}));
46234623

4624+
it('should update the option selected state if the same array is mutated and passed back in',
4625+
fakeAsync(() => {
4626+
const value: string[] = [];
4627+
trigger.click();
4628+
testInstance.control.setValue(value);
4629+
fixture.detectChanges();
4630+
4631+
const optionNodes =
4632+
Array.from<HTMLElement>(overlayContainerElement.querySelectorAll('mat-option'));
4633+
const optionInstances = testInstance.options.toArray();
4634+
4635+
expect(optionNodes.some(option => option.classList.contains('mat-selected'))).toBe(false);
4636+
expect(optionInstances.some(option => option.selected)).toBe(false);
4637+
4638+
value.push('eggs-5');
4639+
testInstance.control.setValue(value);
4640+
fixture.detectChanges();
4641+
4642+
expect(optionNodes[5].classList).toContain('mat-selected');
4643+
expect(optionInstances[5].selected).toBe(true);
4644+
}));
4645+
46244646
});
46254647

46264648
it('should be able to provide default values through an injection token', fakeAsync(() => {

src/material/select/select.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,8 @@ export abstract class _MatSelectBase<C> extends _MatSelectMixinBase implements A
402402
@Input()
403403
get value(): any { return this._value; }
404404
set value(newValue: any) {
405-
if (newValue !== this._value) {
405+
// Always re-assign an array, because it might have been mutated.
406+
if (newValue !== this._value || (this._multiple && Array.isArray(newValue))) {
406407
if (this.options) {
407408
this._setSelectionByValue(newValue);
408409
}

0 commit comments

Comments
 (0)