Skip to content

Commit ce432f4

Browse files
committed
fix(select): emitting change event twice for reset values
Fixes `mat-select` emitting its change event twice when a reset value is selected, as well as when it's selected twice in a row. This PR covers #10859 which would've introduced another issue. Fixes #10675. Fixes #13579.
1 parent e16997c commit ce432f4

File tree

2 files changed

+124
-2
lines changed

2 files changed

+124
-2
lines changed

src/material/select/select.spec.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,6 +2897,44 @@ describe('MatSelect', () => {
28972897
}));
28982898
});
28992899

2900+
describe('with reset option and a form control', () => {
2901+
let fixture: ComponentFixture<SelectWithResetOptionAndFormControl>;
2902+
let options: HTMLElement[];
2903+
2904+
beforeEach(fakeAsync(() => {
2905+
configureMatSelectTestingModule([SelectWithResetOptionAndFormControl]);
2906+
fixture = TestBed.createComponent(SelectWithResetOptionAndFormControl);
2907+
fixture.detectChanges();
2908+
fixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement.click();
2909+
fixture.detectChanges();
2910+
options = Array.from(overlayContainerElement.querySelectorAll('mat-option'));
2911+
}));
2912+
2913+
it('should set the select value', fakeAsync(() => {
2914+
fixture.componentInstance.control.setValue('a');
2915+
fixture.detectChanges();
2916+
expect(fixture.componentInstance.select.value).toBe('a');
2917+
}));
2918+
2919+
it('should reset the control value', fakeAsync(() => {
2920+
fixture.componentInstance.control.setValue('a');
2921+
fixture.detectChanges();
2922+
2923+
options[0].click();
2924+
fixture.detectChanges();
2925+
flush();
2926+
expect(fixture.componentInstance.control.value).toBe(undefined);
2927+
}));
2928+
2929+
it('should reflect the value in the form control', fakeAsync(() => {
2930+
options[1].click();
2931+
fixture.detectChanges();
2932+
flush();
2933+
expect(fixture.componentInstance.select.value).toBe('a');
2934+
expect(fixture.componentInstance.control.value).toBe('a');
2935+
}));
2936+
});
2937+
29002938
describe('without Angular forms', () => {
29012939
beforeEach(async(() => configureMatSelectTestingModule([
29022940
BasicSelectWithoutForms,
@@ -3174,6 +3212,63 @@ describe('MatSelect', () => {
31743212
.toBeFalsy('Expected no value after tabbing away.');
31753213
}));
31763214

3215+
it('should emit once when a reset value is selected', fakeAsync(() => {
3216+
const fixture = TestBed.createComponent(BasicSelectWithoutForms);
3217+
const instance = fixture.componentInstance;
3218+
const spy = jasmine.createSpy('change spy');
3219+
3220+
instance.selectedFood = 'sandwich-2';
3221+
instance.foods[0].value = null;
3222+
fixture.detectChanges();
3223+
3224+
const subscription = instance.select.selectionChange.subscribe(spy);
3225+
3226+
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
3227+
fixture.detectChanges();
3228+
flush();
3229+
3230+
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
3231+
fixture.detectChanges();
3232+
flush();
3233+
3234+
expect(spy).toHaveBeenCalledTimes(1);
3235+
3236+
subscription.unsubscribe();
3237+
}));
3238+
3239+
it('should not emit the change event multiple times when a reset option is ' +
3240+
'selected twice in a row', fakeAsync(() => {
3241+
const fixture = TestBed.createComponent(BasicSelectWithoutForms);
3242+
const instance = fixture.componentInstance;
3243+
const spy = jasmine.createSpy('change spy');
3244+
3245+
instance.foods[0].value = null;
3246+
fixture.detectChanges();
3247+
3248+
const subscription = instance.select.selectionChange.subscribe(spy);
3249+
3250+
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
3251+
fixture.detectChanges();
3252+
flush();
3253+
3254+
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
3255+
fixture.detectChanges();
3256+
flush();
3257+
3258+
expect(spy).not.toHaveBeenCalled();
3259+
3260+
fixture.debugElement.query(By.css('.mat-select-trigger')).nativeElement.click();
3261+
fixture.detectChanges();
3262+
flush();
3263+
3264+
(overlayContainerElement.querySelector('mat-option') as HTMLElement).click();
3265+
fixture.detectChanges();
3266+
flush();
3267+
3268+
expect(spy).not.toHaveBeenCalled();
3269+
3270+
subscription.unsubscribe();
3271+
}));
31773272

31783273
});
31793274

@@ -5302,3 +5397,23 @@ class MultiSelectWithLotsOfOptions {
53025397
this.value = [];
53035398
}
53045399
}
5400+
5401+
5402+
@Component({
5403+
selector: 'basic-select-with-reset',
5404+
template: `
5405+
<mat-form-field>
5406+
<mat-select [formControl]="control">
5407+
<mat-option>Reset</mat-option>
5408+
<mat-option value="a">A</mat-option>
5409+
<mat-option value="b">B</mat-option>
5410+
<mat-option value="c">C</mat-option>
5411+
</mat-select>
5412+
</mat-form-field>
5413+
`
5414+
})
5415+
class SelectWithResetOptionAndFormControl {
5416+
@ViewChild(MatSelect) select: MatSelect;
5417+
@ViewChildren(MatOption) options: QueryList<MatOption>;
5418+
control = new FormControl();
5419+
}

src/material/select/select.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
442442
get value(): any { return this._value; }
443443
set value(newValue: any) {
444444
if (newValue !== this._value) {
445-
this.writeValue(newValue);
445+
if (this.options) {
446+
this._setSelectionByValue(newValue);
447+
}
448+
446449
this._value = newValue;
447450
}
448451
}
@@ -677,6 +680,7 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
677680
*/
678681
writeValue(value: any): void {
679682
if (this.options) {
683+
this._value = value;
680684
this._setSelectionByValue(value);
681685
}
682686
}
@@ -1003,7 +1007,10 @@ export class MatSelect extends _MatSelectMixinBase implements AfterContentInit,
10031007
if (option.value == null && !this._multiple) {
10041008
option.deselect();
10051009
this._selectionModel.clear();
1006-
this._propagateChanges(option.value);
1010+
1011+
if (this.value != null) {
1012+
this._propagateChanges(option.value);
1013+
}
10071014
} else {
10081015
if (wasSelected !== option.selected) {
10091016
option.selected ? this._selectionModel.select(option) :

0 commit comments

Comments
 (0)