From d3dc31c109d584020d535351ac683adbfec7c152 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 31 Mar 2021 14:50:55 +0200 Subject: [PATCH] fix(material/list): dispatching model change event multiple times in single selection mode Fixes that the MDC-based list dispatches its `ngModelChange` event multiple times when the value changes in single selection mode. Fixes #22276. --- .../mdc-list/list-option.ts | 5 ++- .../mdc-list/selection-list.spec.ts | 34 ++++++++++++++++++- src/material/list/selection-list.spec.ts | 33 +++++++++++++++++- src/material/list/selection-list.ts | 5 ++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/material-experimental/mdc-list/list-option.ts b/src/material-experimental/mdc-list/list-option.ts index 435e648ad210..5a9af758fca0 100644 --- a/src/material-experimental/mdc-list/list-option.ts +++ b/src/material-experimental/mdc-list/list-option.ts @@ -121,7 +121,10 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit if (isSelected !== this._selected) { this._setSelected(isSelected); - this._selectionList._reportValueChange(); + + if (isSelected || this._selectionList.multiple) { + this._selectionList._reportValueChange(); + } } } private _selected = false; diff --git a/src/material-experimental/mdc-list/selection-list.spec.ts b/src/material-experimental/mdc-list/selection-list.spec.ts index 4582bd71b55b..96929feec6f7 100644 --- a/src/material-experimental/mdc-list/selection-list.spec.ts +++ b/src/material-experimental/mdc-list/selection-list.spec.ts @@ -1109,6 +1109,34 @@ describe('MDC-based MatSelectionList with forms', () => { expect(listOptions.map(option => option.selected)).toEqual([true, true, true, false, false]); })); + + it('should dispatch one change event per change when updating a single-selection list', + fakeAsync(() => { + fixture.destroy(); + fixture = TestBed.createComponent(SelectionListWithModel); + fixture.componentInstance.multiple = false; + fixture.componentInstance.selectedOptions = ['opt3']; + fixture.detectChanges(); + const options = fixture.debugElement.queryAll(By.directive(MatListOption)) + .map(optionDebugEl => optionDebugEl.nativeElement); + + expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); + + options[0].click(); + fixture.detectChanges(); + tick(); + + expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1); + expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); + + options[1].click(); + fixture.detectChanges(); + tick(); + + expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2); + expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); + })); + }); describe('and formControl', () => { @@ -1412,13 +1440,17 @@ class SelectionListWithOnlyOneOption { @Component({ template: ` - + {{option}} ` }) class SelectionListWithModel { modelChangeSpy = jasmine.createSpy('model change spy'); selectedOptions: string[] = []; + multiple = true; options = ['opt1', 'opt2', 'opt3']; } diff --git a/src/material/list/selection-list.spec.ts b/src/material/list/selection-list.spec.ts index df34d51843c0..9c10f339265f 100644 --- a/src/material/list/selection-list.spec.ts +++ b/src/material/list/selection-list.spec.ts @@ -1341,6 +1341,33 @@ describe('MatSelectionList with forms', () => { expect(selectionListDebug.nativeElement.tabIndex).toBe(-1); }); + it('should dispatch one change event per change when updating a single-selection list', + fakeAsync(() => { + fixture.destroy(); + fixture = TestBed.createComponent(SelectionListWithModel); + fixture.componentInstance.multiple = false; + fixture.componentInstance.selectedOptions = ['opt3']; + fixture.detectChanges(); + const options = fixture.debugElement.queryAll(By.directive(MatListOption)) + .map(optionDebugEl => optionDebugEl.nativeElement); + + expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled(); + + options[0].click(); + fixture.detectChanges(); + tick(); + + expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1); + expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']); + + options[1].click(); + fixture.detectChanges(); + tick(); + + expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2); + expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']); + })); + }); describe('and formControl', () => { @@ -1642,13 +1669,17 @@ class SelectionListWithOnlyOneOption { @Component({ template: ` - + {{option}} ` }) class SelectionListWithModel { modelChangeSpy = jasmine.createSpy('model change spy'); selectedOptions: string[] = []; + multiple = true; options = ['opt1', 'opt2', 'opt3']; } diff --git a/src/material/list/selection-list.ts b/src/material/list/selection-list.ts index 58fd6dae486f..0a7f17835053 100644 --- a/src/material/list/selection-list.ts +++ b/src/material/list/selection-list.ts @@ -186,7 +186,10 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte if (isSelected !== this._selected) { this._setSelected(isSelected); - this.selectionList._reportValueChange(); + + if (isSelected || this.selectionList.multiple) { + this.selectionList._reportValueChange(); + } } }