Skip to content

Commit 941055c

Browse files
committed
fix(material/list): Do not rely on input binding order
This change ensures that setting `value` does not clear the `selected` input until after the first change detection cycle. Ivy sets inputs based on the order they appear in the templates. Fixes #17500
1 parent 3de0b08 commit 941055c

File tree

2 files changed

+48
-14
lines changed

2 files changed

+48
-14
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,18 @@ describe('MatSelectionList with forms', () => {
11911191
expect(testComponent.optionInstances.toArray()[1].selected).toBe(true);
11921192
}));
11931193
});
1194+
1195+
it('can bind both selected and value at the same time', () => {
1196+
const fixture =
1197+
TestBed
1198+
.configureTestingModule(
1199+
{imports: [MatListModule], declarations: [SelectionListWithSelectedOptionAndValue]})
1200+
.createComponent(SelectionListWithSelectedOptionAndValue);
1201+
fixture.detectChanges();
1202+
const listItemEl = fixture.debugElement.query(By.directive(MatListOption))!;
1203+
expect(listItemEl.componentInstance.selected).toBe(true);
1204+
expect(listItemEl.componentInstance.value).toBe(fixture.componentInstance.itemValue);
1205+
});
11941206
});
11951207

11961208

@@ -1277,6 +1289,16 @@ class SelectionListWithDisabledOption {
12771289
class SelectionListWithSelectedOption {
12781290
}
12791291

1292+
@Component({
1293+
template: `
1294+
<mat-selection-list>
1295+
<mat-list-option [selected]="true" [value]="itemValue">Item</mat-list-option>
1296+
</mat-selection-list>`
1297+
})
1298+
class SelectionListWithSelectedOptionAndValue {
1299+
itemValue = 'item1';
1300+
}
1301+
12801302
@Component({template: `
12811303
<mat-selection-list id="selection-list-4">
12821304
<mat-list-option checkboxPosition="after" class="test-focus" id="123">

src/material/list/selection-list.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import {FocusableOption, FocusKeyManager} from '@angular/cdk/a11y';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {SelectionModel} from '@angular/cdk/collections';
1212
import {
13-
SPACE,
13+
A,
14+
DOWN_ARROW,
15+
END,
1416
ENTER,
17+
hasModifierKey,
1518
HOME,
16-
END,
19+
SPACE,
1720
UP_ARROW,
18-
DOWN_ARROW,
19-
A,
20-
hasModifierKey,
2121
} from '@angular/cdk/keycodes';
2222
import {
2323
AfterContentInit,
@@ -32,25 +32,27 @@ import {
3232
forwardRef,
3333
Inject,
3434
Input,
35+
OnChanges,
3536
OnDestroy,
3637
OnInit,
3738
Output,
3839
QueryList,
40+
SimpleChanges,
3941
ViewChild,
4042
ViewEncapsulation,
41-
SimpleChanges,
42-
OnChanges,
4343
} from '@angular/core';
44+
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
4445
import {
45-
CanDisableRipple, CanDisableRippleCtor,
46+
CanDisableRipple,
47+
CanDisableRippleCtor,
4648
MatLine,
47-
setLines,
4849
mixinDisableRipple,
50+
setLines,
4951
ThemePalette,
5052
} from '@angular/material/core';
51-
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
5253
import {Subject} from 'rxjs';
5354
import {takeUntil} from 'rxjs/operators';
55+
5456
import {MatListAvatarCssMatStyler, MatListIconCssMatStyler} from './list';
5557

5658

@@ -114,9 +116,10 @@ export class MatSelectionListChange {
114116
encapsulation: ViewEncapsulation.None,
115117
changeDetection: ChangeDetectionStrategy.OnPush,
116118
})
117-
export class MatListOption extends _MatListOptionMixinBase
118-
implements AfterContentInit, OnDestroy, OnInit, FocusableOption, CanDisableRipple {
119-
119+
export class MatListOption extends _MatListOptionMixinBase implements AfterContentInit, OnChanges,
120+
OnDestroy, OnInit,
121+
FocusableOption,
122+
CanDisableRipple {
120123
private _selected = false;
121124
private _disabled = false;
122125
private _hasFocus = false;
@@ -137,11 +140,16 @@ export class MatListOption extends _MatListOptionMixinBase
137140
set color(newValue: ThemePalette) { this._color = newValue; }
138141
private _color: ThemePalette;
139142

143+
/**
144+
* This is set to true after the first OnChanges cycle so we don't clear the value of `selected`
145+
* in the first cycle.
146+
*/
147+
private _inputsInitialized = false;
140148
/** Value of the option */
141149
@Input()
142150
get value(): any { return this._value; }
143151
set value(newValue: any) {
144-
if (this.selected && newValue !== this.value) {
152+
if (this.selected && newValue !== this.value && this._inputsInitialized) {
145153
this.selected = false;
146154
}
147155

@@ -202,6 +210,10 @@ export class MatListOption extends _MatListOptionMixinBase
202210
});
203211
}
204212

213+
ngOnChanges() {
214+
this._inputsInitialized = true;
215+
}
216+
205217
ngAfterContentInit() {
206218
setLines(this._lines, this._element);
207219
}

0 commit comments

Comments
 (0)