Skip to content

Commit 463ac9e

Browse files
crisbetovivian-hu-zz
authored andcommittedJan 17, 2019
fix(list): disableRipple on list input not taking effect after init (#14836)
Fixes the `disableRipple` on the list not having an effect if it's changed through the input after init. Fixes #14824.
1 parent 28e4eff commit 463ac9e

File tree

3 files changed

+121
-10
lines changed

3 files changed

+121
-10
lines changed
 

‎src/lib/list/list.spec.ts

+61-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import {async, TestBed} from '@angular/core/testing';
1+
import {async, TestBed, fakeAsync, tick} from '@angular/core/testing';
22
import {Component, QueryList, ViewChildren} from '@angular/core';
3+
import {defaultRippleAnimationConfig} from '@angular/material/core';
4+
import {dispatchMouseEvent} from '@angular/cdk/testing';
35
import {By} from '@angular/platform-browser';
46
import {MatListItem, MatListModule} from './index';
57

6-
78
describe('MatList', () => {
9+
// Default ripple durations used for testing.
10+
const {enterDuration, exitDuration} = defaultRippleAnimationConfig;
811

912
beforeEach(async(() => {
1013
TestBed.configureTestingModule({
@@ -207,6 +210,62 @@ describe('MatList', () => {
207210
expect(items.every(item => item._isRippleDisabled())).toBe(true);
208211
});
209212

213+
it('should disable item ripples when list ripples are disabled via the input in nav list',
214+
fakeAsync(() => {
215+
const fixture = TestBed.createComponent(NavListWithOneAnchorItem);
216+
fixture.detectChanges();
217+
218+
const rippleTarget = fixture.nativeElement.querySelector('.mat-list-item-content');
219+
220+
dispatchMouseEvent(rippleTarget, 'mousedown');
221+
dispatchMouseEvent(rippleTarget, 'mouseup');
222+
223+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
224+
.toBe(1, 'Expected ripples to be enabled by default.');
225+
226+
// Wait for the ripples to go away.
227+
tick(enterDuration + exitDuration);
228+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
229+
.toBe(0, 'Expected ripples to go away.');
230+
231+
fixture.componentInstance.disableListRipple = true;
232+
fixture.detectChanges();
233+
234+
dispatchMouseEvent(rippleTarget, 'mousedown');
235+
dispatchMouseEvent(rippleTarget, 'mouseup');
236+
237+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
238+
.toBe(0, 'Expected no ripples after list ripples are disabled.');
239+
}));
240+
241+
it('should disable item ripples when list ripples are disabled via the input in an action list',
242+
fakeAsync(() => {
243+
const fixture = TestBed.createComponent(ActionListWithoutType);
244+
fixture.detectChanges();
245+
246+
const rippleTarget = fixture.nativeElement.querySelector('.mat-list-item-content');
247+
248+
dispatchMouseEvent(rippleTarget, 'mousedown');
249+
dispatchMouseEvent(rippleTarget, 'mouseup');
250+
251+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
252+
.toBe(1, 'Expected ripples to be enabled by default.');
253+
254+
// Wait for the ripples to go away.
255+
tick(enterDuration + exitDuration);
256+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
257+
.toBe(0, 'Expected ripples to go away.');
258+
259+
fixture.componentInstance.disableListRipple = true;
260+
fixture.detectChanges();
261+
262+
dispatchMouseEvent(rippleTarget, 'mousedown');
263+
dispatchMouseEvent(rippleTarget, 'mouseup');
264+
265+
expect(rippleTarget.querySelectorAll('.mat-ripple-element').length)
266+
.toBe(0, 'Expected no ripples after list ripples are disabled.');
267+
}));
268+
210269
});
211270

212271

‎src/lib/list/list.ts

+49-4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {
1717
Optional,
1818
QueryList,
1919
ViewEncapsulation,
20+
OnChanges,
21+
OnDestroy,
22+
ChangeDetectorRef,
2023
} from '@angular/core';
2124
import {
2225
CanDisableRipple,
@@ -25,6 +28,8 @@ import {
2528
setLines,
2629
mixinDisableRipple,
2730
} from '@angular/material/core';
31+
import {Subject} from 'rxjs';
32+
import {takeUntil} from 'rxjs/operators';
2833

2934
// Boilerplate for applying mixins to MatList.
3035
/** @docs-private */
@@ -52,7 +57,19 @@ export const _MatListItemMixinBase: CanDisableRippleCtor & typeof MatListItemBas
5257
encapsulation: ViewEncapsulation.None,
5358
changeDetection: ChangeDetectionStrategy.OnPush,
5459
})
55-
export class MatNavList extends _MatListMixinBase implements CanDisableRipple {}
60+
export class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges,
61+
OnDestroy {
62+
/** Emits when the state of the list changes. */
63+
_stateChanges = new Subject<void>();
64+
65+
ngOnChanges() {
66+
this._stateChanges.next();
67+
}
68+
69+
ngOnDestroy() {
70+
this._stateChanges.complete();
71+
}
72+
}
5673

5774
@Component({
5875
moduleId: module.id,
@@ -67,7 +84,10 @@ export class MatNavList extends _MatListMixinBase implements CanDisableRipple {}
6784
encapsulation: ViewEncapsulation.None,
6885
changeDetection: ChangeDetectionStrategy.OnPush,
6986
})
70-
export class MatList extends _MatListMixinBase implements CanDisableRipple {
87+
export class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
88+
/** Emits when the state of the list changes. */
89+
_stateChanges = new Subject<void>();
90+
7191
/**
7292
* @deprecated _elementRef parameter to be made required.
7393
* @breaking-change 8.0.0
@@ -94,6 +114,14 @@ export class MatList extends _MatListMixinBase implements CanDisableRipple {
94114

95115
return null;
96116
}
117+
118+
ngOnChanges() {
119+
this._stateChanges.next();
120+
}
121+
122+
ngOnDestroy() {
123+
this._stateChanges.complete();
124+
}
97125
}
98126

99127
/**
@@ -143,17 +171,20 @@ export class MatListSubheaderCssMatStyler {}
143171
changeDetection: ChangeDetectionStrategy.OnPush,
144172
})
145173
export class MatListItem extends _MatListItemMixinBase implements AfterContentInit,
146-
CanDisableRipple {
174+
CanDisableRipple, OnDestroy {
147175
private _isInteractiveList: boolean = false;
148176
private _list?: MatNavList | MatList;
177+
private _destroyed = new Subject<void>();
149178

150179
@ContentChildren(MatLine) _lines: QueryList<MatLine>;
151180
@ContentChild(MatListAvatarCssMatStyler) _avatar: MatListAvatarCssMatStyler;
152181
@ContentChild(MatListIconCssMatStyler) _icon: MatListIconCssMatStyler;
153182

154183
constructor(private _element: ElementRef<HTMLElement>,
155184
@Optional() navList?: MatNavList,
156-
@Optional() list?: MatList) {
185+
@Optional() list?: MatList,
186+
// @breaking-change 8.0.0 `_changeDetectorRef` to be made into a required parameter.
187+
_changeDetectorRef?: ChangeDetectorRef) {
157188
super();
158189
this._isInteractiveList = !!(navList || (list && list._getListType() === 'action-list'));
159190
this._list = navList || list;
@@ -165,12 +196,26 @@ export class MatListItem extends _MatListItemMixinBase implements AfterContentIn
165196
if (element.nodeName.toLowerCase() === 'button' && !element.hasAttribute('type')) {
166197
element.setAttribute('type', 'button');
167198
}
199+
200+
// @breaking-change 8.0.0 Remove null check for _changeDetectorRef.
201+
if (this._list && _changeDetectorRef) {
202+
// React to changes in the state of the parent list since
203+
// some of the item's properties depend on it (e.g. `disableRipple`).
204+
this._list._stateChanges.pipe(takeUntil(this._destroyed)).subscribe(() => {
205+
_changeDetectorRef.markForCheck();
206+
});
207+
}
168208
}
169209

170210
ngAfterContentInit() {
171211
setLines(this._lines, this._element);
172212
}
173213

214+
ngOnDestroy() {
215+
this._destroyed.next();
216+
this._destroyed.complete();
217+
}
218+
174219
/** Whether this list item should show a ripple effect when clicked. */
175220
_isRippleDisabled() {
176221
return !this._isInteractiveList || this.disableRipple ||

‎tools/public_api_guard/lib/list.d.ts

+11-4
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ export declare const _MatSelectionListMixinBase: CanDisableRippleCtor & typeof M
88

99
export declare const MAT_SELECTION_LIST_VALUE_ACCESSOR: any;
1010

11-
export declare class MatList extends _MatListMixinBase implements CanDisableRipple {
11+
export declare class MatList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
12+
_stateChanges: Subject<void>;
1213
constructor(_elementRef?: ElementRef<HTMLElement> | undefined);
1314
_getListType(): 'list' | 'action-list' | null;
15+
ngOnChanges(): void;
16+
ngOnDestroy(): void;
1417
}
1518

1619
export declare class MatListAvatarCssMatStyler {
@@ -22,14 +25,15 @@ export declare class MatListBase {
2225
export declare class MatListIconCssMatStyler {
2326
}
2427

25-
export declare class MatListItem extends _MatListItemMixinBase implements AfterContentInit, CanDisableRipple {
28+
export declare class MatListItem extends _MatListItemMixinBase implements AfterContentInit, CanDisableRipple, OnDestroy {
2629
_avatar: MatListAvatarCssMatStyler;
2730
_icon: MatListIconCssMatStyler;
2831
_lines: QueryList<MatLine>;
29-
constructor(_element: ElementRef<HTMLElement>, navList?: MatNavList, list?: MatList);
32+
constructor(_element: ElementRef<HTMLElement>, navList?: MatNavList, list?: MatList, _changeDetectorRef?: ChangeDetectorRef);
3033
_getHostElement(): HTMLElement;
3134
_isRippleDisabled(): boolean;
3235
ngAfterContentInit(): void;
36+
ngOnDestroy(): void;
3337
}
3438

3539
export declare class MatListItemBase {
@@ -71,7 +75,10 @@ export declare class MatListOptionBase {
7175
export declare class MatListSubheaderCssMatStyler {
7276
}
7377

74-
export declare class MatNavList extends _MatListMixinBase implements CanDisableRipple {
78+
export declare class MatNavList extends _MatListMixinBase implements CanDisableRipple, OnChanges, OnDestroy {
79+
_stateChanges: Subject<void>;
80+
ngOnChanges(): void;
81+
ngOnDestroy(): void;
7582
}
7683

7784
export declare class MatSelectionList extends _MatSelectionListMixinBase implements FocusableOption, CanDisableRipple, AfterContentInit, ControlValueAccessor, OnDestroy {

0 commit comments

Comments
 (0)
Please sign in to comment.