Skip to content

Commit 4ac64da

Browse files
devversionjosephperrott
authored andcommitted
fix(selection-list): disabling list doesn't disable ripples of options (#11955)
1 parent a3dba76 commit 4ac64da

File tree

2 files changed

+48
-5
lines changed

2 files changed

+48
-5
lines changed

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
ViewChildren,
1414
} from '@angular/core';
1515
import {async, ComponentFixture, fakeAsync, TestBed, tick, flush} from '@angular/core/testing';
16+
import {MatRipple} from '@angular/material/core';
1617
import {By} from '@angular/platform-browser';
1718
import {
1819
MatListModule,
@@ -626,6 +627,23 @@ describe('MatSelectionList without forms', () => {
626627

627628
expect(selectList.selected.length).toBe(0);
628629
});
630+
631+
it('should update state of options if list state has changed', () => {
632+
// To verify that the template of the list options has been re-rendered after the disabled
633+
// property of the selection list has been updated, the ripple directive can be used.
634+
// Inspecting the host classes of the options doesn't work because those update as part
635+
// of the parent template (of the selection-list).
636+
const listOptionRipple = listOption[2].query(By.directive(MatRipple)).injector.get(MatRipple);
637+
638+
expect(listOptionRipple.disabled)
639+
.toBe(true, 'Expected ripples of list option to be disabled');
640+
641+
fixture.componentInstance.disabled = false;
642+
fixture.detectChanges();
643+
644+
expect(listOptionRipple.disabled)
645+
.toBe(false, 'Expected ripples of list option to be enabled');
646+
});
629647
});
630648

631649
describe('with checkbox position after', () => {
@@ -977,7 +995,7 @@ class SelectionListWithCheckboxPositionAfter {
977995
}
978996

979997
@Component({template: `
980-
<mat-selection-list id="selection-list-3" [disabled]=true>
998+
<mat-selection-list id="selection-list-3" [disabled]="disabled">
981999
<mat-list-option checkboxPosition="after">
9821000
Inbox (disabled selection-option)
9831001
</mat-list-option>
@@ -992,6 +1010,7 @@ class SelectionListWithCheckboxPositionAfter {
9921010
</mat-list-option>
9931011
</mat-selection-list>`})
9941012
class SelectionListWithListDisabled {
1013+
disabled: boolean = true;
9951014
}
9961015

9971016
@Component({template: `

src/lib/list/selection-list.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,9 @@ import {
3030
ViewEncapsulation,
3131
} from '@angular/core';
3232
import {
33-
CanDisable,
3433
CanDisableRipple,
3534
MatLine,
3635
MatLineSetter,
37-
mixinDisabled,
3836
mixinDisableRipple,
3937
} from '@angular/material/core';
4038
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@@ -43,7 +41,7 @@ import {Subscription} from 'rxjs';
4341

4442
/** @docs-private */
4543
export class MatSelectionListBase {}
46-
export const _MatSelectionListMixinBase = mixinDisableRipple(mixinDisabled(MatSelectionListBase));
44+
export const _MatSelectionListMixinBase = mixinDisableRipple(MatSelectionListBase);
4745

4846
/** @docs-private */
4947
export class MatListOptionBase {}
@@ -238,6 +236,15 @@ export class MatListOption extends _MatListOptionMixinBase
238236
this._changeDetector.markForCheck();
239237
return true;
240238
}
239+
240+
/**
241+
* Notifies Angular that the option needs to be checked in the next change detection run. Mainly
242+
* used to trigger an update of the list option if the disabled state of the selection list
243+
* changed.
244+
*/
245+
_markForCheck() {
246+
this._changeDetector.markForCheck();
247+
}
241248
}
242249

243250

@@ -265,7 +272,7 @@ export class MatListOption extends _MatListOptionMixinBase
265272
changeDetection: ChangeDetectionStrategy.OnPush
266273
})
267274
export class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption,
268-
CanDisable, CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy {
275+
CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy {
269276

270277
/** The FocusKeyManager which handles focus. */
271278
_keyManager: FocusKeyManager<MatListOption>;
@@ -287,6 +294,22 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
287294
*/
288295
@Input() compareWith: (o1: any, o2: any) => boolean;
289296

297+
/** Whether the selection list is disabled. */
298+
@Input()
299+
get disabled(): boolean { return this._disabled; }
300+
set disabled(value: boolean) {
301+
this._disabled = coerceBooleanProperty(value);
302+
303+
// The `MatSelectionList` and `MatListOption` are using the `OnPush` change detection
304+
// strategy. Therefore the options will not check for any changes if the `MatSelectionList`
305+
// changed its state. Since we know that a change to `disabled` property of the list affects
306+
// the state of the options, we manually mark each option for check.
307+
if (this.options) {
308+
this.options.forEach(option => option._markForCheck());
309+
}
310+
}
311+
private _disabled: boolean = false;
312+
290313
/** The currently selected options. */
291314
selectedOptions: SelectionModel<MatListOption> = new SelectionModel<MatListOption>(true);
292315

@@ -296,6 +319,7 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu
296319
/** Used for storing the values that were assigned before the options were initialized. */
297320
private _tempValues: string[]|null;
298321

322+
/** Subscription to sync value changes in the SelectionModel back to the SelectionList. */
299323
private _modelChanges = Subscription.EMPTY;
300324

301325
/** View to model callback that should be called if the list or its options lost focus. */

0 commit comments

Comments
 (0)