Skip to content

Commit

Permalink
fix(material/core): stop manually instantiating MatRipple directive (#…
Browse files Browse the repository at this point in the history
…29630)

Previously we had to manually instantiate `MatRipple` in order to maintain backwards compatibility, but it was problematic because it would break whenever we tried to use DI in `MatRipple` and it prevented us from switching to the `inject` function.

Instantiating `MatRipple` is no longer necessary after #29622 so these changes switch to creating an internal `RippleRenderer` instead.
  • Loading branch information
crisbeto committed Aug 23, 2024
1 parent 485bd99 commit d0e178b
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 46 deletions.
100 changes: 56 additions & 44 deletions src/material/core/private/ripple-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
*/

import {DOCUMENT} from '@angular/common';
import {ANIMATION_MODULE_TYPE, Injectable, NgZone, OnDestroy, inject} from '@angular/core';
import {
ANIMATION_MODULE_TYPE,
ElementRef,
Injectable,
NgZone,
OnDestroy,
inject,
} from '@angular/core';
import {MAT_RIPPLE_GLOBAL_OPTIONS, MatRipple} from '../ripple';
MAT_RIPPLE_GLOBAL_OPTIONS,
RippleRenderer,
RippleTarget,
defaultRippleAnimationConfig,
} from '../ripple';
import {Platform, _getEventTarget} from '@angular/cdk/platform';

/** The options for the MatRippleLoader's event listeners. */
Expand Down Expand Up @@ -55,7 +53,10 @@ export class MatRippleLoader implements OnDestroy {
private _globalRippleOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, {optional: true});
private _platform = inject(Platform);
private _ngZone = inject(NgZone);
private _hosts = new Map<HTMLElement, MatRipple>();
private _hosts = new Map<
HTMLElement,
{renderer: RippleRenderer; target: RippleTarget; hasSetUpEvents: boolean}
>();

constructor() {
this._ngZone.runOutsideAngular(() => {
Expand All @@ -65,7 +66,7 @@ export class MatRippleLoader implements OnDestroy {
});
}

ngOnDestroy() {
ngOnDestroy(): void {
const hosts = this._hosts.keys();

for (const host of hosts) {
Expand Down Expand Up @@ -115,13 +116,15 @@ export class MatRippleLoader implements OnDestroy {

// If the ripple has already been instantiated, just disable it.
if (ripple) {
ripple.disabled = disabled;
return;
}
ripple.target.rippleDisabled = disabled;

// Otherwise, set an attribute so we know what the
// disabled state should be when the ripple is initialized.
if (disabled) {
if (!disabled && !ripple.hasSetUpEvents) {
ripple.hasSetUpEvents = true;
ripple.renderer.setupTriggerEvents(host);
}
} else if (disabled) {
// Otherwise, set an attribute so we know what the
// disabled state should be when the ripple is initialized.
host.setAttribute(matRippleDisabled, '');
} else {
host.removeAttribute(matRippleDisabled);
Expand All @@ -148,50 +151,59 @@ export class MatRippleLoader implements OnDestroy {
};

/** Creates a MatRipple and appends it to the given element. */
private _createRipple(host: HTMLElement): MatRipple | undefined {
if (!this._document) {
private _createRipple(host: HTMLElement): void {
if (!this._document || this._hosts.has(host)) {
return;
}

const existingRipple = this._hosts.get(host);
if (existingRipple) {
return existingRipple;
}

// Create the ripple element.
host.querySelector('.mat-ripple')?.remove();
const rippleEl = this._document.createElement('span');
rippleEl.classList.add('mat-ripple', host.getAttribute(matRippleClassName)!);
host.append(rippleEl);

// Create the MatRipple.
const ripple = new MatRipple(
new ElementRef(rippleEl),
this._ngZone,
this._platform,
this._globalRippleOptions ? this._globalRippleOptions : undefined,
this._animationMode ? this._animationMode : undefined,
);
ripple._isInitialized = true;
ripple.trigger = host;
ripple.centered = host.hasAttribute(matRippleCentered);
ripple.disabled = host.hasAttribute(matRippleDisabled);
this.attachRipple(host, ripple);
return ripple;
}
const isNoopAnimations = this._animationMode === 'NoopAnimations';
const globalOptions = this._globalRippleOptions;
const enterDuration = isNoopAnimations
? 0
: globalOptions?.animation?.enterDuration ?? defaultRippleAnimationConfig.enterDuration;
const exitDuration = isNoopAnimations
? 0
: globalOptions?.animation?.exitDuration ?? defaultRippleAnimationConfig.exitDuration;
const target: RippleTarget = {
rippleDisabled:
isNoopAnimations || globalOptions?.disabled || host.hasAttribute(matRippleDisabled),
rippleConfig: {
centered: host.hasAttribute(matRippleCentered),
terminateOnPointerUp: globalOptions?.terminateOnPointerUp,
animation: {
enterDuration,
exitDuration,
},
},
};

const renderer = new RippleRenderer(target, this._ngZone, rippleEl, this._platform);
const hasSetUpEvents = !target.rippleDisabled;

if (hasSetUpEvents) {
renderer.setupTriggerEvents(host);
}

this._hosts.set(host, {
target,
renderer,
hasSetUpEvents,
});

attachRipple(host: HTMLElement, ripple: MatRipple): void {
host.removeAttribute(matRippleUninitialized);
this._hosts.set(host, ripple);
}

destroyRipple(host: HTMLElement) {
destroyRipple(host: HTMLElement): void {
const ripple = this._hosts.get(host);

if (ripple) {
// Since this directive is created manually, it needs to be destroyed manually too.
// tslint:disable-next-line:no-lifecycle-invocation
ripple.ngOnDestroy();
ripple.renderer._removeTriggerEvents();
this._hosts.delete(host);
}
}
Expand Down
2 changes: 0 additions & 2 deletions tools/public_api_guard/material/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,8 +402,6 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget {
// @public
export class MatRippleLoader implements OnDestroy {
constructor();
// (undocumented)
attachRipple(host: HTMLElement, ripple: MatRipple): void;
configureRipple(host: HTMLElement, config: {
className?: string;
centered?: boolean;
Expand Down

0 comments on commit d0e178b

Please sign in to comment.