Skip to content

Commit 3274e49

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 b488b39 commit 3274e49

File tree

3 files changed

+82
-75
lines changed

3 files changed

+82
-75
lines changed

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

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
import {ElementRef, NgZone} from '@angular/core';
1010
import {Platform} from '@angular/cdk/platform';
11+
import {MatRipple} from './ripple';
1112
import {RippleRef, RippleState} from './ripple-ref';
1213

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

@@ -58,13 +58,11 @@ export class RippleRenderer {
5858
/** Time in milliseconds when the last touchstart event happened. */
5959
private _lastTouchStartEvent: number;
6060

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

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

7775
this._triggerEvents.set('touchstart', this.onTouchStart.bind(this));
7876
this._triggerEvents.set('touchend', this.onPointerUp.bind(this));
79-
80-
// By default use the host element as trigger element.
81-
this.setTriggerElement(this._containerElement);
8277
}
8378
}
8479

@@ -168,21 +163,18 @@ export class RippleRenderer {
168163
this._activeRipples.forEach(ripple => ripple.fadeOut());
169164
}
170165

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

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

187179
this._triggerElement = element;
188180
}
@@ -192,22 +184,23 @@ export class RippleRenderer {
192184
const isSyntheticEvent = this._lastTouchStartEvent &&
193185
Date.now() < this._lastTouchStartEvent + IGNORE_MOUSE_EVENTS_TIMEOUT;
194186

195-
if (!this.rippleDisabled && !isSyntheticEvent) {
187+
if (!this._ripple.disabled && !isSyntheticEvent) {
196188
this._isPointerDown = true;
197-
this.fadeInRipple(event.clientX, event.clientY, this.rippleConfig);
189+
this.fadeInRipple(event.clientX, event.clientY, this._ripple.rippleConfig);
198190
}
199191
}
200192

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

210-
this.fadeInRipple(event.touches[0].clientX, event.touches[0].clientY, this.rippleConfig);
202+
this.fadeInRipple(
203+
event.touches[0].clientX, event.touches[0].clientY, this._ripple.rippleConfig);
211204
}
212205
}
213206

@@ -232,10 +225,17 @@ export class RippleRenderer {
232225
this._ngZone.runOutsideAngular(() => setTimeout(fn, delay));
233226
}
234227

228+
/** Removes previously registered event listeners from the trigger element. */
229+
_removeTriggerListeners() {
230+
if (this._triggerElement) {
231+
this._triggerEvents.forEach((fn, type) => {
232+
this._triggerElement!.removeEventListener(type, fn);
233+
});
234+
}
235+
}
235236
}
236237

237238
/** Enforces a style recalculation of a DOM element by computing its styles. */
238-
// TODO(devversion): Move into global utility function.
239239
function enforceStyleRecalculation(element: HTMLElement) {
240240
// Enforce a style recalculation by calling `getComputedStyle` and accessing any property.
241241
// 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)