From 6fbd3e89e64ae606db6d277ffff9bbc8e3006829 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Thu, 28 Jun 2018 17:04:50 +0200 Subject: [PATCH] fix(selection-list): disabling list doesn't disable ripples of options * Fixes that dynamically changing the `[disabled]` binding does not update the state of the list options (e.g. ripple disabled state; checkbox disabled styling) Fixes #9952 --- src/lib/list/selection-list.spec.ts | 21 ++++++++++++++++++- src/lib/list/selection-list.ts | 32 +++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/lib/list/selection-list.spec.ts b/src/lib/list/selection-list.spec.ts index 6d0e9837940f..c03e1b096817 100644 --- a/src/lib/list/selection-list.spec.ts +++ b/src/lib/list/selection-list.spec.ts @@ -13,6 +13,7 @@ import { ViewChildren, } from '@angular/core'; import {async, ComponentFixture, fakeAsync, TestBed, tick, flush} from '@angular/core/testing'; +import {MatRipple} from '@angular/material/core'; import {By} from '@angular/platform-browser'; import { MatListModule, @@ -626,6 +627,23 @@ describe('MatSelectionList without forms', () => { expect(selectList.selected.length).toBe(0); }); + + it('should update state of options if list state has changed', () => { + // To verify that the template of the list options has been re-rendered after the disabled + // property of the selection list has been updated, the ripple directive can be used. + // Inspecting the host classes of the options doesn't work because those update as part + // of the parent template (of the selection-list). + const listOptionRipple = listOption[2].query(By.directive(MatRipple)).injector.get(MatRipple); + + expect(listOptionRipple.disabled) + .toBe(true, 'Expected ripples of list option to be disabled'); + + fixture.componentInstance.disabled = false; + fixture.detectChanges(); + + expect(listOptionRipple.disabled) + .toBe(false, 'Expected ripples of list option to be enabled'); + }); }); describe('with checkbox position after', () => { @@ -977,7 +995,7 @@ class SelectionListWithCheckboxPositionAfter { } @Component({template: ` - + Inbox (disabled selection-option) @@ -992,6 +1010,7 @@ class SelectionListWithCheckboxPositionAfter { `}) class SelectionListWithListDisabled { + disabled: boolean = true; } @Component({template: ` diff --git a/src/lib/list/selection-list.ts b/src/lib/list/selection-list.ts index 10d81fc7571c..0b15ee8f0d93 100644 --- a/src/lib/list/selection-list.ts +++ b/src/lib/list/selection-list.ts @@ -30,11 +30,9 @@ import { ViewEncapsulation, } from '@angular/core'; import { - CanDisable, CanDisableRipple, MatLine, MatLineSetter, - mixinDisabled, mixinDisableRipple, } from '@angular/material/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; @@ -43,7 +41,7 @@ import {Subscription} from 'rxjs'; /** @docs-private */ export class MatSelectionListBase {} -export const _MatSelectionListMixinBase = mixinDisableRipple(mixinDisabled(MatSelectionListBase)); +export const _MatSelectionListMixinBase = mixinDisableRipple(MatSelectionListBase); /** @docs-private */ export class MatListOptionBase {} @@ -238,6 +236,15 @@ export class MatListOption extends _MatListOptionMixinBase this._changeDetector.markForCheck(); return true; } + + /** + * Notifies Angular that the option needs to be checked in the next change detection run. Mainly + * used to trigger an update of the list option if the disabled state of the selection list + * changed. + */ + _markForCheck() { + this._changeDetector.markForCheck(); + } } @@ -265,7 +272,7 @@ export class MatListOption extends _MatListOptionMixinBase changeDetection: ChangeDetectionStrategy.OnPush }) export class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption, - CanDisable, CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy { + CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy { /** The FocusKeyManager which handles focus. */ _keyManager: FocusKeyManager; @@ -287,6 +294,22 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu */ @Input() compareWith: (o1: any, o2: any) => boolean; + /** Whether the selection list is disabled. */ + @Input() + get disabled(): boolean { return this._disabled; } + set disabled(value: boolean) { + this._disabled = coerceBooleanProperty(value); + + // The `MatSelectionList` and `MatListOption` are using the `OnPush` change detection + // strategy. Therefore the options will not check for any changes if the `MatSelectionList` + // changed its state. Since we know that a change to `disabled` property of the list affects + // the state of the options, we manually mark each option for check. + if (this.options) { + this.options.forEach(option => option._markForCheck()); + } + } + private _disabled: boolean = false; + /** The currently selected options. */ selectedOptions: SelectionModel = new SelectionModel(true); @@ -296,6 +319,7 @@ export class MatSelectionList extends _MatSelectionListMixinBase implements Focu /** Used for storing the values that were assigned before the options were initialized. */ private _tempValues: string[]|null; + /** Subscription to sync value changes in the SelectionModel back to the SelectionList. */ private _modelChanges = Subscription.EMPTY; /** View to model callback that should be called if the list or its options lost focus. */