Skip to content

Commit a6fbf1d

Browse files
committed
perf(ripple): do not register events if ripples are disabled
* No longer registers trigger event listeners if the ripples are disabled initially. Fixes #8854.
1 parent 230b297 commit a6fbf1d

File tree

3 files changed

+83
-76
lines changed

3 files changed

+83
-76
lines changed

src/lib/core/ripple/ripple-renderer.ts

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
*/
88
import {ElementRef, NgZone} from '@angular/core';
99
import {Platform, supportsPassiveEventListeners} from '@angular/cdk/platform';
10+
import {MatRipple} from './ripple';
1011
import {RippleRef, RippleState} from './ripple-ref';
1112

12-
1313
/** Fade-in duration for the ripples. Can be modified with the speedFactor option. */
1414
export const RIPPLE_FADE_IN_DURATION = 450;
1515

@@ -60,13 +60,11 @@ export class RippleRenderer {
6060
/** Options that apply to all the event listeners that are bound by the renderer. */
6161
private _eventOptions = supportsPassiveEventListeners() ? ({passive: true} as any) : false;
6262

63-
/** Ripple config for all ripples created by events. */
64-
rippleConfig: RippleConfig = {};
65-
66-
/** Whether mouse ripples should be created or not. */
67-
rippleDisabled: boolean = false;
63+
constructor(private _ripple: MatRipple,
64+
private _ngZone: NgZone,
65+
elementRef: ElementRef,
66+
platform: Platform) {
6867

69-
constructor(elementRef: ElementRef, private _ngZone: NgZone, platform: Platform) {
7068
// Only do anything if we're on the browser.
7169
if (platform.isBrowser) {
7270
this._containerElement = elementRef.nativeElement;
@@ -78,9 +76,6 @@ export class RippleRenderer {
7876

7977
this._triggerEvents.set('touchstart', this.onTouchStart);
8078
this._triggerEvents.set('touchend', this.onPointerUp);
81-
82-
// By default use the host element as trigger element.
83-
this.setTriggerElement(this._containerElement);
8479
}
8580
}
8681

@@ -170,22 +165,19 @@ export class RippleRenderer {
170165
this._activeRipples.forEach(ripple => ripple.fadeOut());
171166
}
172167

173-
/** Sets the trigger element and registers the mouse events. */
174-
setTriggerElement(element: HTMLElement | null) {
175-
// Remove all previously register event listeners from the trigger element.
176-
if (this._triggerElement) {
177-
this._triggerEvents.forEach((fn, type) => {
178-
this._triggerElement!.removeEventListener(type, fn, this._eventOptions);
179-
});
168+
/** Sets up the trigger event listeners */
169+
setupTriggerEvents(element: HTMLElement) {
170+
if (!element || element === this._triggerElement) {
171+
return;
180172
}
181173

182-
if (element) {
183-
// If the element is not null, register all event listeners on the trigger element.
184-
this._ngZone.runOutsideAngular(() => {
185-
this._triggerEvents.forEach((fn, type) =>
186-
element.addEventListener(type, fn, this._eventOptions));
187-
});
188-
}
174+
// Remove all previously registered event listeners from the trigger element.
175+
this._removeTriggerListeners();
176+
177+
this._ngZone.runOutsideAngular(() => {
178+
this._triggerEvents.forEach((fn, type) =>
179+
element.addEventListener(type, fn, this._eventOptions));
180+
});
189181

190182
this._triggerElement = element;
191183
}
@@ -195,22 +187,23 @@ export class RippleRenderer {
195187
const isSyntheticEvent = this._lastTouchStartEvent &&
196188
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
197189

198-
if (!this.rippleDisabled && !isSyntheticEvent) {
190+
if (!this._ripple.disabled && !isSyntheticEvent) {
199191
this._isPointerDown = true;
200-
this.fadeInRipple(event.clientX, event.clientY, this.rippleConfig);
192+
this.fadeInRipple(event.clientX, event.clientY, this._ripple.rippleConfig);
201193
}
202194
}
203195

204196
/** Function being called whenever the trigger is being pressed using touch. */
205197
private onTouchStart = (event: TouchEvent) => {
206-
if (!this.rippleDisabled) {
198+
if (!this._ripple.disabled) {
207199
// Some browsers fire mouse events after a `touchstart` event. Those synthetic mouse
208200
// events will launch a second ripple if we don't ignore mouse events for a specific
209201
// time after a touchstart event.
210202
this._lastTouchStartEvent = Date.now();
211203
this._isPointerDown = true;
212204

213-
this.fadeInRipple(event.touches[0].clientX, event.touches[0].clientY, this.rippleConfig);
205+
this.fadeInRipple(
206+
event.touches[0].clientX, event.touches[0].clientY, this._ripple.rippleConfig);
214207
}
215208
}
216209

@@ -235,10 +228,17 @@ export class RippleRenderer {
235228
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
236229
}
237230

231+
/** Removes previously registered event listeners from the trigger element. */
232+
_removeTriggerListeners() {
233+
if (this._triggerElement) {
234+
this._triggerEvents.forEach((fn, type) => {
235+
this._triggerElement!.removeEventListener(type, fn, this._eventOptions);
236+
});
237+
}
238+
}
238239
}
239240

240241
/** Enforces a style recalculation of a DOM element by computing its styles. */
241-
// TODO(devversion): Move into global utility function.
242242
function enforceStyleRecalculation(element: HTMLElement) {
243243
// Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
244244
// Calling `getPropertyValue` is important to let optimizers know that this is not a noop.

src/lib/core/ripple/ripple.ts

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,20 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {Platform} from '@angular/cdk/platform';
910
import {
1011
Directive,
1112
ElementRef,
12-
Input,
1313
Inject,
14+
InjectionToken,
15+
Input,
1416
NgZone,
15-
OnChanges,
16-
SimpleChanges,
1717
OnDestroy,
18-
InjectionToken,
18+
OnInit,
1919
Optional,
2020
} from '@angular/core';
21-
import {Platform} from '@angular/cdk/platform';
22-
import {RippleConfig, RippleRenderer} from './ripple-renderer';
2321
import {RippleRef} from './ripple-ref';
22+
import {RippleConfig, RippleRenderer} from './ripple-renderer';
2423

2524
/** Configurable options for `matRipple`. */
2625
export interface RippleGlobalOptions {
@@ -50,28 +49,20 @@ export const MAT_RIPPLE_GLOBAL_OPTIONS =
5049
'[class.mat-ripple-unbounded]': 'unbounded'
5150
}
5251
})
53-
export class MatRipple implements OnChanges, OnDestroy {
52+
export class MatRipple implements OnInit, OnDestroy {
5453

55-
/**
56-
* The element that triggers the ripple when click events are received. Defaults to the
57-
* directive's host element.
58-
*/
59-
// Prevent TS metadata emit from referencing HTMLElement in ripple.js
60-
// Otherwise running this code in a Node environment (e.g Universal) will not work.
61-
@Input('matRippleTrigger') trigger: HTMLElement|HTMLElement;
54+
/** Custom color for all ripples. */
55+
@Input('matRippleColor') color: string;
56+
57+
/** Whether the ripples should be visible outside the component's bounds. */
58+
@Input('matRippleUnbounded') unbounded: boolean;
6259

6360
/**
6461
* Whether the ripple always originates from the center of the host element's bounds, rather
6562
* than originating from the location of the click event.
6663
*/
6764
@Input('matRippleCentered') centered: boolean;
6865

69-
/**
70-
* Whether click events will not trigger the ripple. Ripples can be still launched manually
71-
* by using the `launch()` method.
72-
*/
73-
@Input('matRippleDisabled') disabled: boolean;
74-
7566
/**
7667
* If set, the radius in pixels of foreground ripples when fully expanded. If unset, the radius
7768
* will be the distance from the center of the ripple to the furthest corner of the host element's
@@ -86,45 +77,59 @@ export class MatRipple implements OnChanges, OnDestroy {
8677
*/
8778
@Input('matRippleSpeedFactor') speedFactor: number = 1;
8879

89-
/** Custom color for ripples. */
90-
@Input('matRippleColor') color: string;
80+
/**
81+
* Whether click events will not trigger the ripple. Ripples can be still launched manually
82+
* by using the `launch()` method.
83+
*/
84+
@Input('matRippleDisabled')
85+
get disabled() { return this._disabled || !!this._globalOptions.disabled; }
86+
set disabled(value: boolean) {
87+
this._disabled = value;
88+
this._setupTriggerEventsIfEnabled();
89+
}
90+
private _disabled: boolean = false;
9191

92-
/** Whether foreground ripples should be visible outside the component's bounds. */
93-
@Input('matRippleUnbounded') unbounded: boolean;
92+
/**
93+
* The element that triggers the ripple when click events are received.
94+
* Defaults to the directive's host element.
95+
*/
96+
@Input('matRippleTrigger')
97+
get trigger() { return this._trigger || this._elementRef.nativeElement; }
98+
set trigger(trigger: HTMLElement) {
99+
this._trigger = trigger;
100+
this._setupTriggerEventsIfEnabled();
101+
}
102+
private _trigger: HTMLElement;
94103

95104
/** Renderer for the ripple DOM manipulations. */
96105
private _rippleRenderer: RippleRenderer;
97106

98107
/** Options that are set globally for all ripples. */
99108
private _globalOptions: RippleGlobalOptions;
100109

101-
constructor(
102-
elementRef: ElementRef,
103-
ngZone: NgZone,
104-
platform: Platform,
105-
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions
106-
) {
107-
this._rippleRenderer = new RippleRenderer(elementRef, ngZone, platform);
108-
this._globalOptions = globalOptions ? globalOptions : {};
110+
/** Whether ripple directive is initialized and the input bindings are set. */
111+
private _isInitialized: boolean = false;
109112

110-
this._updateRippleRenderer();
111-
}
113+
constructor(private _elementRef: ElementRef,
114+
ngZone: NgZone,
115+
platform: Platform,
116+
@Optional() @Inject(MAT_RIPPLE_GLOBAL_OPTIONS) globalOptions: RippleGlobalOptions) {
112117

113-
ngOnChanges(changes: SimpleChanges) {
114-
if (changes['trigger'] && this.trigger) {
115-
this._rippleRenderer.setTriggerElement(this.trigger);
116-
}
118+
this._globalOptions = globalOptions || {};
119+
this._rippleRenderer = new RippleRenderer(this, ngZone, _elementRef, platform);
120+
}
117121

118-
this._updateRippleRenderer();
122+
ngOnInit() {
123+
this._isInitialized = true;
124+
this._setupTriggerEventsIfEnabled();
119125
}
120126

121127
ngOnDestroy() {
122-
// Set the trigger element to null to cleanup all listeners.
123-
this._rippleRenderer.setTriggerElement(null);
128+
this._rippleRenderer._removeTriggerListeners();
124129
}
125130

126131
/** Launches a manual ripple at the specified position. */
127-
launch(x: number, y: number, config: RippleConfig = this.rippleConfig): RippleRef {
132+
launch(x: number, y: number, config: RippleConfig = this): RippleRef {
128133
return this._rippleRenderer.fadeInRipple(x, y, config);
129134
}
130135

@@ -143,9 +148,11 @@ export class MatRipple implements OnChanges, OnDestroy {
143148
};
144149
}
145150

146-
/** Updates the ripple renderer with the latest ripple configuration. */
147-
_updateRippleRenderer() {
148-
this._rippleRenderer.rippleDisabled = this._globalOptions.disabled || this.disabled;
149-
this._rippleRenderer.rippleConfig = this.rippleConfig;
151+
/** Sets up the the trigger event listeners if ripples are enabled. */
152+
private _setupTriggerEventsIfEnabled() {
153+
if (!this.disabled && this._isInitialized) {
154+
this._rippleRenderer.setupTriggerEvents(this.trigger);
155+
}
150156
}
151157
}
158+

src/lib/tabs/tab-nav-bar/tab-nav-bar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,6 @@ export class MatTabLink extends _MatTabLinkMixinBase
215215
set disableRipple(value: boolean) {
216216
this._disableRipple = value;
217217
this._tabLinkRipple.disabled = this.disableRipple;
218-
this._tabLinkRipple._updateRippleRenderer();
219218
}
220219

221220
constructor(private _tabNavBar: MatTabNav,
@@ -229,6 +228,7 @@ export class MatTabLink extends _MatTabLinkMixinBase
229228
// Manually create a ripple instance that uses the tab link element as trigger element.
230229
// Notice that the lifecycle hooks for the ripple config won't be called anymore.
231230
this._tabLinkRipple = new MatRipple(_elementRef, ngZone, platform, globalOptions);
231+
this._tabLinkRipple.ngOnInit();
232232

233233
this.tabIndex = parseInt(tabIndex) || 0;
234234
}

0 commit comments

Comments
 (0)