Skip to content

Commit 5dfe4d2

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 0bd626d commit 5dfe4d2

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
@@ -2904,6 +2904,44 @@ describe('MatSelect', () => {
29042904
}));
29052905
});
29062906

2907+
describe('with reset option and a form control', () => {
2908+
let fixture: ComponentFixture<SelectWithResetOptionAndFormControl>;
2909+
let options: HTMLElement[];
2910+
2911+
beforeEach(fakeAsync(() => {
2912+
configureMatSelectTestingModule([SelectWithResetOptionAndFormControl]);
2913+
fixture = TestBed.createComponent(SelectWithResetOptionAndFormControl);
2914+
fixture.detectChanges();
2915+
fixture.debugElement.query(By.css('.mat-select-trigger'))!.nativeElement.click();
2916+
fixture.detectChanges();
2917+
options = Array.from(overlayContainerElement.querySelectorAll('mat-option'));
2918+
}));
2919+
2920+
it('should set the select value', fakeAsync(() => {
2921+
fixture.componentInstance.control.setValue('a');
2922+
fixture.detectChanges();
2923+
expect(fixture.componentInstance.select.value).toBe('a');
2924+
}));
2925+
2926+
it('should reset the control value', fakeAsync(() => {
2927+
fixture.componentInstance.control.setValue('a');
2928+
fixture.detectChanges();
2929+
2930+
options[0].click();
2931+
fixture.detectChanges();
2932+
flush();
2933+
expect(fixture.componentInstance.control.value).toBe(undefined);
2934+
}));
2935+
2936+
it('should reflect the value in the form control', fakeAsync(() => {
2937+
options[1].click();
2938+
fixture.detectChanges();
2939+
flush();
2940+
expect(fixture.componentInstance.select.value).toBe('a');
2941+
expect(fixture.componentInstance.control.value).toBe('a');
2942+
}));
2943+
});
2944+
29072945
describe('without Angular forms', () => {
29082946
beforeEach(async(() => configureMatSelectTestingModule([
29092947
BasicSelectWithoutForms,
@@ -3181,6 +3219,63 @@ describe('MatSelect', () => {
31813219
.toBeFalsy('Expected no value after tabbing away.');
31823220
}));
31833221

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

31853280
});
31863281

@@ -5313,3 +5408,23 @@ class MultiSelectWithLotsOfOptions {
53135408
this.value = [];
53145409
}
53155410
}
5411+
5412+
5413+
@Component({
5414+
selector: 'basic-select-with-reset',
5415+
template: `
5416+
<mat-form-field>
5417+
<mat-select [formControl]="control">
5418+
<mat-option>Reset</mat-option>
5419+
<mat-option value="a">A</mat-option>
5420+
<mat-option value="b">B</mat-option>
5421+
<mat-option value="c">C</mat-option>
5422+
</mat-select>
5423+
</mat-form-field>
5424+
`
5425+
})
5426+
class SelectWithResetOptionAndFormControl {
5427+
@ViewChild(MatSelect) select: MatSelect;
5428+
@ViewChildren(MatOption) options: QueryList<MatOption>;
5429+
control = new FormControl();
5430+
}

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)