fix(platform-browser): ensure that Hammer loader is called only once (#40911)

Currently, the function that is provided through `HAMMER_LOADER` is called the
same number of times as the `HammerGesturesPlugin.addEventListener` method is called
(until the Hammer is loaded).

This commit adds a class property in which the loader call is saved, thereby
preventing multiple calls to the loader function.

PR Close #25995

PR Close #40911
This commit is contained in:
arturovt 2021-02-18 20:53:57 +02:00 committed by atscott
parent aed2782c4a
commit 228b5f73b1
2 changed files with 37 additions and 13 deletions

View File

@ -162,6 +162,8 @@ export class HammerGestureConfig {
*/
@Injectable()
export class HammerGesturesPlugin extends EventManagerPlugin {
private _loaderPromise: Promise<void>|null = null;
constructor(
@Inject(DOCUMENT) doc: any,
@Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig, private console: Console,
@ -175,9 +177,11 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
}
if (!(window as any).Hammer && !this.loader) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
this.console.warn(
`The "${eventName}" event cannot be bound because Hammer.JS is not ` +
`loaded and no custom loader has been specified.`);
}
return false;
}
@ -191,6 +195,7 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
// If Hammer is not present but a loader is specified, we defer adding the event listener
// until Hammer is loaded.
if (!(window as any).Hammer && this.loader) {
this._loaderPromise = this._loaderPromise || this.loader();
// This `addEventListener` method returns a function to remove the added listener.
// Until Hammer is loaded, the returned function needs to *cancel* the registration rather
// than remove anything.
@ -199,12 +204,14 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
cancelRegistration = true;
};
this.loader()
this._loaderPromise
.then(() => {
// If Hammer isn't actually loaded when the custom loader resolves, give up.
if (!(window as any).Hammer) {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
this.console.warn(
`The custom HAMMER_LOADER completed, but Hammer.JS is not present.`);
}
deregister = () => {};
return;
}
@ -216,9 +223,11 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
}
})
.catch(() => {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
this.console.warn(
`The "${eventName}" event cannot be bound because the custom ` +
`Hammer.JS loader failed.`);
}
deregister = () => {};
});

View File

@ -67,6 +67,8 @@ import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-brow
ngZone = z;
}));
let loaderCalled = 0;
beforeEach(() => {
originalHammerGlobal = (window as any).Hammer;
(window as any).Hammer = undefined;
@ -76,10 +78,13 @@ import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-brow
off: jasmine.createSpy('mc.off'),
};
loader = () => new Promise((resolve, reject) => {
loader = () => {
loaderCalled++;
return new Promise((resolve, reject) => {
resolveLoader = resolve;
failLoader = reject;
});
};
// Make the hammer config return a fake hammer instance
const hammerConfig = new HammerGestureConfig();
@ -95,9 +100,19 @@ import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-brow
});
afterEach(() => {
loaderCalled = 0;
(window as any).Hammer = originalHammerGlobal;
});
it('should call the loader provider only once', () => {
plugin.addEventListener(someElement, 'swipe', () => {});
plugin.addEventListener(someElement, 'panleft', () => {});
plugin.addEventListener(someElement, 'panright', () => {});
// Ensure that the loader is called only once, because previouly
// it was called the same number of times as `addEventListener` was called.
expect(loaderCalled).toEqual(1);
});
it('should not log a warning when HammerJS is not loaded', () => {
plugin.addEventListener(someElement, 'swipe', () => {});
expect(fakeConsole.warn).not.toHaveBeenCalled();