From ea67b9be9f62184f26d89dad41d5a2ebda67e7b7 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Mon, 21 Mar 2022 08:47:38 +0100 Subject: [PATCH] fix(material/select): value set through property not being propagated to value accessor Fixes values set through the `value` property not being propagated to the `value` in the `ControlValueAccessor`. Fixes #10214. --- .../mdc-select/select.spec.ts | 11 +++++++ src/material/select/select.spec.ts | 11 +++++++ src/material/select/select.ts | 31 +++++++++++++------ 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/material-experimental/mdc-select/select.spec.ts b/src/material-experimental/mdc-select/select.spec.ts index 1f89790daea9..246493586229 100644 --- a/src/material-experimental/mdc-select/select.spec.ts +++ b/src/material-experimental/mdc-select/select.spec.ts @@ -2155,6 +2155,17 @@ describe('MDC-based MatSelect', () => { .withContext(`Expected label to have an asterisk, as control was required.`) .toBeTruthy(); })); + + it('should propagate the value set through the `value` property to the form field', fakeAsync(() => { + const control = fixture.componentInstance.control; + + expect(control.value).toBeFalsy(); + + fixture.componentInstance.select.value = 'pizza-1'; + fixture.detectChanges(); + + expect(control.value).toBe('pizza-1'); + })); }); describe('disabled behavior', () => { diff --git a/src/material/select/select.spec.ts b/src/material/select/select.spec.ts index a0cd6a819e10..11c25bebadd9 100644 --- a/src/material/select/select.spec.ts +++ b/src/material/select/select.spec.ts @@ -2170,6 +2170,17 @@ describe('MatSelect', () => { .not.withContext(`Expected label to have an asterisk, as control was required.`) .toBeNull(); })); + + it('should propagate the value set through the `value` property to the form field', fakeAsync(() => { + const control = fixture.componentInstance.control; + + expect(control.value).toBeFalsy(); + + fixture.componentInstance.select.value = 'pizza-1'; + fixture.detectChanges(); + + expect(control.value).toBe('pizza-1'); + })); }); describe('disabled behavior', () => { diff --git a/src/material/select/select.ts b/src/material/select/select.ts index 661e3f016c82..fd0241d767d6 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -423,13 +423,10 @@ export abstract class _MatSelectBase return this._value; } set value(newValue: any) { - // Always re-assign an array, because it might have been mutated. - if (newValue !== this._value || (this._multiple && Array.isArray(newValue))) { - if (this.options) { - this._setSelectionByValue(newValue); - } + const hasAssigned = this._assignValue(newValue); - this._value = newValue; + if (hasAssigned) { + this._onChange(newValue); } } private _value: any; @@ -661,7 +658,7 @@ export abstract class _MatSelectBase * @param value New value to be written to the model. */ writeValue(value: any): void { - this.value = value; + this._assignValue(value); } /** @@ -886,10 +883,10 @@ export abstract class _MatSelectBase throw getMatSelectNonArrayValueError(); } - value.forEach((currentValue: any) => this._selectValue(currentValue)); + value.forEach((currentValue: any) => this._selectOptionByValue(currentValue)); this._sortValues(); } else { - const correspondingOption = this._selectValue(value); + const correspondingOption = this._selectOptionByValue(value); // Shift focus to the active item. Note that we shouldn't do this in multiple // mode, because we don't know what option the user interacted with last. @@ -909,7 +906,7 @@ export abstract class _MatSelectBase * Finds and selects and option based on its value. * @returns Option that has the corresponding value. */ - private _selectValue(value: any): MatOption | undefined { + private _selectOptionByValue(value: any): MatOption | undefined { const correspondingOption = this.options.find((option: MatOption) => { // Skip options that are already in the model. This allows us to handle cases // where the same primitive value is selected multiple times. @@ -936,6 +933,20 @@ export abstract class _MatSelectBase return correspondingOption; } + /** Assigns a specific value to the select. Returns whether the value has changed. */ + private _assignValue(newValue: any | any[]): boolean { + // Always re-assign an array, because it might have been mutated. + if (newValue !== this._value || (this._multiple && Array.isArray(newValue))) { + if (this.options) { + this._setSelectionByValue(newValue); + } + + this._value = newValue; + return true; + } + return false; + } + /** Sets up a key manager to listen to keyboard events on the overlay panel. */ private _initKeyManager() { this._keyManager = new ActiveDescendantKeyManager(this.options)