Skip to content

Commit 4301fb0

Browse files
atscottmmalerba
authored andcommitted
fix(material/list): Do not rely on input binding order (#17501)
* 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 * fixup! fix(material/list): Do not rely on input binding order
1 parent 0248ca9 commit 4301fb0

File tree

2 files changed

+41
-14
lines changed

2 files changed

+41
-14
lines changed

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ describe('MatSelectionList without forms', () => {
3939
SelectionListWithListDisabled,
4040
SelectionListWithOnlyOneOption,
4141
SelectionListWithIndirectChildOptions,
42+
SelectionListWithSelectedOptionAndValue,
4243
],
4344
});
4445

@@ -594,6 +595,14 @@ describe('MatSelectionList without forms', () => {
594595
.toBe(0, 'Expected no ripples after list ripples are disabled.');
595596
}));
596597

598+
it('can bind both selected and value at the same time', () => {
599+
const componentFixture = TestBed.createComponent(SelectionListWithSelectedOptionAndValue);
600+
componentFixture.detectChanges();
601+
const listItemEl = componentFixture.debugElement.query(By.directive(MatListOption))!;
602+
expect(listItemEl.componentInstance.selected).toBe(true);
603+
expect(listItemEl.componentInstance.value).toBe(componentFixture.componentInstance.itemValue);
604+
});
605+
597606
});
598607

599608
describe('with list option selected', () => {
@@ -1277,6 +1286,16 @@ class SelectionListWithDisabledOption {
12771286
class SelectionListWithSelectedOption {
12781287
}
12791288

1289+
@Component({
1290+
template: `
1291+
<mat-selection-list>
1292+
<mat-list-option [selected]="true" [value]="itemValue">Item</mat-list-option>
1293+
</mat-selection-list>`
1294+
})
1295+
class SelectionListWithSelectedOptionAndValue {
1296+
itemValue = 'item1';
1297+
}
1298+
12801299
@Component({template: `
12811300
<mat-selection-list id="selection-list-4">
12821301
<mat-list-option checkboxPosition="after" class="test-focus" id="123">

src/material/list/selection-list.ts

Lines changed: 22 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,9 @@ 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, OnDestroy,
120+
OnInit, FocusableOption,
121+
CanDisableRipple {
120122
private _selected = false;
121123
private _disabled = false;
122124
private _hasFocus = false;
@@ -137,11 +139,16 @@ export class MatListOption extends _MatListOptionMixinBase
137139
set color(newValue: ThemePalette) { this._color = newValue; }
138140
private _color: ThemePalette;
139141

142+
/**
143+
* This is set to true after the first OnChanges cycle so we don't clear the value of `selected`
144+
* in the first cycle.
145+
*/
146+
private _inputsInitialized = false;
140147
/** Value of the option */
141148
@Input()
142149
get value(): any { return this._value; }
143150
set value(newValue: any) {
144-
if (this.selected && newValue !== this.value) {
151+
if (this.selected && newValue !== this.value && this._inputsInitialized) {
145152
this.selected = false;
146153
}
147154

@@ -200,6 +207,7 @@ export class MatListOption extends _MatListOptionMixinBase
200207
this._changeDetector.markForCheck();
201208
}
202209
});
210+
this._inputsInitialized = true;
203211
}
204212

205213
ngAfterContentInit() {

0 commit comments

Comments
 (0)