Skip to content

Commit 11ff7ba

Browse files
crisbetommalerba
authored andcommitted
fix(material/list): dispatching model change event multiple times in single selection mode (#22376)
Fixes that the MDC-based list dispatches its `ngModelChange` event multiple times when the value changes in single selection mode. Fixes #22276. (cherry picked from commit 7702177)
1 parent bf8dc1b commit 11ff7ba

File tree

4 files changed

+73
-4
lines changed

4 files changed

+73
-4
lines changed

src/material-experimental/mdc-list/list-option.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,10 @@ export class MatListOption extends MatListItemBase implements ListOption, OnInit
121121

122122
if (isSelected !== this._selected) {
123123
this._setSelected(isSelected);
124-
this._selectionList._reportValueChange();
124+
125+
if (isSelected || this._selectionList.multiple) {
126+
this._selectionList._reportValueChange();
127+
}
125128
}
126129
}
127130
private _selected = false;

src/material-experimental/mdc-list/selection-list.spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,34 @@ describe('MDC-based MatSelectionList with forms', () => {
11051105

11061106
expect(listOptions.map(option => option.selected)).toEqual([true, true, true, false, false]);
11071107
}));
1108+
1109+
it('should dispatch one change event per change when updating a single-selection list',
1110+
fakeAsync(() => {
1111+
fixture.destroy();
1112+
fixture = TestBed.createComponent(SelectionListWithModel);
1113+
fixture.componentInstance.multiple = false;
1114+
fixture.componentInstance.selectedOptions = ['opt3'];
1115+
fixture.detectChanges();
1116+
const options = fixture.debugElement.queryAll(By.directive(MatListOption))
1117+
.map(optionDebugEl => optionDebugEl.nativeElement);
1118+
1119+
expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled();
1120+
1121+
options[0].click();
1122+
fixture.detectChanges();
1123+
tick();
1124+
1125+
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1);
1126+
expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']);
1127+
1128+
options[1].click();
1129+
fixture.detectChanges();
1130+
tick();
1131+
1132+
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2);
1133+
expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']);
1134+
}));
1135+
11081136
});
11091137

11101138
describe('and formControl', () => {
@@ -1408,13 +1436,17 @@ class SelectionListWithOnlyOneOption {
14081436

14091437
@Component({
14101438
template: `
1411-
<mat-selection-list [(ngModel)]="selectedOptions" (ngModelChange)="modelChangeSpy()">
1439+
<mat-selection-list
1440+
[(ngModel)]="selectedOptions"
1441+
(ngModelChange)="modelChangeSpy()"
1442+
[multiple]="multiple">
14121443
<mat-list-option *ngFor="let option of options" [value]="option">{{option}}</mat-list-option>
14131444
</mat-selection-list>`
14141445
})
14151446
class SelectionListWithModel {
14161447
modelChangeSpy = jasmine.createSpy('model change spy');
14171448
selectedOptions: string[] = [];
1449+
multiple = true;
14181450
options = ['opt1', 'opt2', 'opt3'];
14191451
}
14201452

src/material/list/selection-list.spec.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,33 @@ describe('MatSelectionList with forms', () => {
13391339
expect(selectionListDebug.nativeElement.tabIndex).toBe(-1);
13401340
});
13411341

1342+
it('should dispatch one change event per change when updating a single-selection list',
1343+
fakeAsync(() => {
1344+
fixture.destroy();
1345+
fixture = TestBed.createComponent(SelectionListWithModel);
1346+
fixture.componentInstance.multiple = false;
1347+
fixture.componentInstance.selectedOptions = ['opt3'];
1348+
fixture.detectChanges();
1349+
const options = fixture.debugElement.queryAll(By.directive(MatListOption))
1350+
.map(optionDebugEl => optionDebugEl.nativeElement);
1351+
1352+
expect(fixture.componentInstance.modelChangeSpy).not.toHaveBeenCalled();
1353+
1354+
options[0].click();
1355+
fixture.detectChanges();
1356+
tick();
1357+
1358+
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(1);
1359+
expect(fixture.componentInstance.selectedOptions).toEqual(['opt1']);
1360+
1361+
options[1].click();
1362+
fixture.detectChanges();
1363+
tick();
1364+
1365+
expect(fixture.componentInstance.modelChangeSpy).toHaveBeenCalledTimes(2);
1366+
expect(fixture.componentInstance.selectedOptions).toEqual(['opt2']);
1367+
}));
1368+
13421369
});
13431370

13441371
describe('and formControl', () => {
@@ -1640,13 +1667,17 @@ class SelectionListWithOnlyOneOption {
16401667

16411668
@Component({
16421669
template: `
1643-
<mat-selection-list [(ngModel)]="selectedOptions" (ngModelChange)="modelChangeSpy()">
1670+
<mat-selection-list
1671+
[(ngModel)]="selectedOptions"
1672+
(ngModelChange)="modelChangeSpy()"
1673+
[multiple]="multiple">
16441674
<mat-list-option *ngFor="let option of options" [value]="option">{{option}}</mat-list-option>
16451675
</mat-selection-list>`
16461676
})
16471677
class SelectionListWithModel {
16481678
modelChangeSpy = jasmine.createSpy('model change spy');
16491679
selectedOptions: string[] = [];
1680+
multiple = true;
16501681
options = ['opt1', 'opt2', 'opt3'];
16511682
}
16521683

src/material/list/selection-list.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,10 @@ export class MatListOption extends _MatListOptionMixinBase implements AfterConte
186186

187187
if (isSelected !== this._selected) {
188188
this._setSelected(isSelected);
189-
this.selectionList._reportValueChange();
189+
190+
if (isSelected || this.selectionList.multiple) {
191+
this.selectionList._reportValueChange();
192+
}
190193
}
191194
}
192195

0 commit comments

Comments
 (0)