Skip to content

Commit 574cfa6

Browse files
committed
fix(list): matching item not preselected if added after init
Fixes a `mat-list-option` that is added after initialization not being selected, even if it matches the control value. Fixes #16062.
1 parent 0908be7 commit 574cfa6

File tree

2 files changed

+48
-18
lines changed

2 files changed

+48
-18
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,21 @@ describe('MatSelectionList with forms', () => {
10981098
expect(fixture.componentInstance.formControl.value).toEqual(['opt2']);
10991099
}));
11001100

1101+
it('should mark options added at a later point as selected', fakeAsync(() => {
1102+
fixture.componentInstance.formControl.setValue(['opt4']);
1103+
fixture.detectChanges();
1104+
1105+
fixture.componentInstance.renderExtraOption = true;
1106+
fixture.detectChanges();
1107+
tick();
1108+
1109+
listOptions = fixture.debugElement.queryAll(By.directive(MatListOption))
1110+
.map(optionDebugEl => optionDebugEl.componentInstance);
1111+
1112+
expect(listOptions.length).toBe(4);
1113+
expect(listOptions[3].selected).toBe(true);
1114+
}));
1115+
11011116
});
11021117

11031118
describe('preselected values', () => {
@@ -1285,12 +1300,14 @@ class SelectionListWithModel {
12851300
<mat-list-option value="opt1">Option 1</mat-list-option>
12861301
<mat-list-option value="opt2">Option 2</mat-list-option>
12871302
<mat-list-option value="opt3">Option 3</mat-list-option>
1303+
<mat-list-option value="opt4" *ngIf="renderExtraOption">Option 4</mat-list-option>
12881304
</mat-selection-list>
12891305
`
12901306
})
12911307
class SelectionListWithFormControl {
12921308
formControl = new FormControl();
12931309
renderList = true;
1310+
renderExtraOption = false;
12941311
}
12951312

12961313

src/material/list/selection-list.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ import {
4949
ThemePalette,
5050
} from '@angular/material/core';
5151
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
52-
import {Subscription} from 'rxjs';
52+
import {Subject} from 'rxjs';
53+
import {takeUntil} from 'rxjs/operators';
5354
import {MatListAvatarCssMatStyler, MatListIconCssMatStyler} from './list';
5455

5556

@@ -103,8 +104,8 @@ export class MatSelectionListChange {
103104
// its theme. The accent theme palette is the default and doesn't need to be set.
104105
'[class.mat-primary]': 'color === "primary"',
105106
'[class.mat-warn]': 'color === "warn"',
106-
'[attr.aria-selected]': 'selected.toString()',
107-
'[attr.aria-disabled]': 'disabled.toString()',
107+
'[attr.aria-selected]': 'selected',
108+
'[attr.aria-disabled]': 'disabled',
108109
},
109110
templateUrl: 'list-option.html',
110111
encapsulation: ViewEncapsulation.None,
@@ -359,17 +360,17 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
359360
/** View to model callback that should be called whenever the selected options change. */
360361
private _onChange: (value: any) => void = (_: any) => {};
361362

362-
/** Used for storing the values that were assigned before the options were initialized. */
363-
private _tempValues: string[]|null;
363+
/** Keeps track of the currently-selected value. */
364+
private _value: string[]|null;
364365

365-
/** Subscription to sync value changes in the SelectionModel back to the SelectionList. */
366-
private _modelChanges = Subscription.EMPTY;
366+
/** Emits when the list has been destroyed. */
367+
private _destroyed = new Subject<void>();
367368

368369
/** View to model callback that should be called if the list or its options lost focus. */
369370
_onTouched: () => void = () => {};
370371

371372
/** Whether the list has been destroyed. */
372-
private _destroyed: boolean;
373+
private _isDestroyed: boolean;
373374

374375
constructor(private _element: ElementRef<HTMLElement>, @Attribute('tabindex') tabIndex: string) {
375376
super();
@@ -385,13 +386,22 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
385386
.skipPredicate(() => false)
386387
.withAllowedModifierKeys(['shiftKey']);
387388

388-
if (this._tempValues) {
389-
this._setOptionsFromValues(this._tempValues);
390-
this._tempValues = null;
389+
if (this._value) {
390+
this._setOptionsFromValues(this._value);
391391
}
392392

393+
this.options.changes.pipe(takeUntil(this._destroyed)).subscribe(() => {
394+
if (this._value) {
395+
// Needs to be deferred, because we're changing bindings as the result of a query
396+
// change which results in a "Changed after checked error".
397+
Promise.resolve().then(() => {
398+
this._setOptionsFromValues(this._value || []);
399+
});
400+
}
401+
});
402+
393403
// Sync external changes to the model back to the options.
394-
this._modelChanges = this.selectedOptions.onChange.subscribe(event => {
404+
this.selectedOptions.onChange.pipe(takeUntil(this._destroyed)).subscribe(event => {
395405
if (event.added) {
396406
for (let item of event.added) {
397407
item.selected = true;
@@ -417,8 +427,9 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
417427
}
418428

419429
ngOnDestroy() {
420-
this._destroyed = true;
421-
this._modelChanges.unsubscribe();
430+
this._destroyed.next();
431+
this._destroyed.complete();
432+
this._isDestroyed = true;
422433
}
423434

424435
/** Focuses the selection list. */
@@ -504,8 +515,10 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
504515
// Stop reporting value changes after the list has been destroyed. This avoids
505516
// cases where the list might wrongly reset its value once it is removed, but
506517
// the form control is still live.
507-
if (this.options && !this._destroyed) {
508-
this._onChange(this._getSelectedOptionValues());
518+
if (this.options && !this._isDestroyed) {
519+
const value = this._getSelectedOptionValues();
520+
this._onChange(value);
521+
this._value = value;
509522
}
510523
}
511524

@@ -516,10 +529,10 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
516529

517530
/** Implemented as part of ControlValueAccessor. */
518531
writeValue(values: string[]): void {
532+
this._value = values;
533+
519534
if (this.options) {
520535
this._setOptionsFromValues(values || []);
521-
} else {
522-
this._tempValues = values;
523536
}
524537
}
525538

0 commit comments

Comments
 (0)