Skip to content

Commit 7702177

Browse files
authored
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.
1 parent dd1d28f commit 7702177

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
@@ -1109,6 +1109,34 @@ describe('MDC-based MatSelectionList with forms', () => {
11091109

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

11141142
describe('and formControl', () => {
@@ -1412,13 +1440,17 @@ class SelectionListWithOnlyOneOption {
14121440

14131441
@Component({
14141442
template: `
1415-
<mat-selection-list [(ngModel)]="selectedOptions" (ngModelChange)="modelChangeSpy()">
1443+
<mat-selection-list
1444+
[(ngModel)]="selectedOptions"
1445+
(ngModelChange)="modelChangeSpy()"
1446+
[multiple]="multiple">
14161447
<mat-list-option *ngFor="let option of options" [value]="option">{{option}}</mat-list-option>
14171448
</mat-selection-list>`
14181449
})
14191450
class SelectionListWithModel {
14201451
modelChangeSpy = jasmine.createSpy('model change spy');
14211452
selectedOptions: string[] = [];
1453+
multiple = true;
14221454
options = ['opt1', 'opt2', 'opt3'];
14231455
}
14241456

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

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

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

13461373
describe('and formControl', () => {
@@ -1642,13 +1669,17 @@ class SelectionListWithOnlyOneOption {
16421669

16431670
@Component({
16441671
template: `
1645-
<mat-selection-list [(ngModel)]="selectedOptions" (ngModelChange)="modelChangeSpy()">
1672+
<mat-selection-list
1673+
[(ngModel)]="selectedOptions"
1674+
(ngModelChange)="modelChangeSpy()"
1675+
[multiple]="multiple">
16461676
<mat-list-option *ngFor="let option of options" [value]="option">{{option}}</mat-list-option>
16471677
</mat-selection-list>`
16481678
})
16491679
class SelectionListWithModel {
16501680
modelChangeSpy = jasmine.createSpy('model change spy');
16511681
selectedOptions: string[] = [];
1682+
multiple = true;
16521683
options = ['opt1', 'opt2', 'opt3'];
16531684
}
16541685

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)