diff --git a/.pullapprove.yml b/.pullapprove.yml
index 4b9523367017f3..9f8066522f06dd 100644
--- a/.pullapprove.yml
+++ b/.pullapprove.yml
@@ -243,6 +243,8 @@ groups:
'aio/content/examples/component-interaction/**/{*,.*}',
'aio/content/images/guide/component-interaction/**/{*,.*}',
'aio/content/guide/component-overview.md',
+ 'aio/content/examples/custom-events/**/{*,.*}',
+ 'aio/content/guide/custom-events-management.md',
'aio/content/errors/*.md',
'aio/content/examples/errors/**/{*,.*}',
'aio/content/examples/component-overview/**/{*,.*}',
diff --git a/aio/content/examples/custom-events/BUILD.bazel b/aio/content/examples/custom-events/BUILD.bazel
new file mode 100644
index 00000000000000..507219e360c903
--- /dev/null
+++ b/aio/content/examples/custom-events/BUILD.bazel
@@ -0,0 +1,7 @@
+load("//aio/content/examples:examples.bzl", "docs_example")
+
+package(default_visibility = ["//visibility:public"])
+
+docs_example(
+ name = "custom-events",
+)
diff --git a/aio/content/examples/custom-events/e2e/src/app.e2e-spec.ts b/aio/content/examples/custom-events/e2e/src/app.e2e-spec.ts
new file mode 100644
index 00000000000000..f026ffd9e04366
--- /dev/null
+++ b/aio/content/examples/custom-events/e2e/src/app.e2e-spec.ts
@@ -0,0 +1,11 @@
+import { browser, element, by } from 'protractor';
+
+describe('Component Overview', () => {
+
+ beforeAll(() => browser.get(''));
+
+ it('should show the propagation stopped button ', async () => {
+ expect(await element(by.css('#stoping')).getText()).toEqual('Propagation stopped');
+ });
+
+});
diff --git a/aio/content/examples/custom-events/example-config.json b/aio/content/examples/custom-events/example-config.json
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/aio/content/examples/custom-events/src/app/app.component.ts b/aio/content/examples/custom-events/src/app/app.component.ts
new file mode 100644
index 00000000000000..59a076083a9abf
--- /dev/null
+++ b/aio/content/examples/custom-events/src/app/app.component.ts
@@ -0,0 +1,19 @@
+import {Component, HostListener} from '@angular/core';
+import {ChildComponent} from './child.component';
+
+@Component({
+ selector: 'app-root',
+ standalone: true,
+ imports: [ChildComponent],
+ template: `
+
+ `,
+})
+export class AppComponent {
+ name = 'Angular';
+
+ @HostListener('click', ['$event'])
+ onClick(event: Event) {
+ console.log('click parent', event);
+ }
+}
diff --git a/aio/content/examples/custom-events/src/app/child.component.ts b/aio/content/examples/custom-events/src/app/child.component.ts
new file mode 100644
index 00000000000000..e9154feeec3d98
--- /dev/null
+++ b/aio/content/examples/custom-events/src/app/child.component.ts
@@ -0,0 +1,16 @@
+import {Component} from '@angular/core';
+
+@Component({
+ selector: 'app-child',
+ standalone: true,
+ template: `
+
+
+
+ `,
+})
+export class ChildComponent {
+ onClick(event: Event) {
+ console.log('click child', event?.target);
+ }
+}
diff --git a/aio/content/examples/custom-events/src/app/stop-event-plugin.ts b/aio/content/examples/custom-events/src/app/stop-event-plugin.ts
new file mode 100644
index 00000000000000..e85d068b563294
--- /dev/null
+++ b/aio/content/examples/custom-events/src/app/stop-event-plugin.ts
@@ -0,0 +1,26 @@
+/* eslint-disable @typescript-eslint/ban-types */
+import {Injectable} from '@angular/core';
+import {EventManagerPlugin} from '@angular/platform-browser';
+
+@Injectable()
+export class StopEventPlugin extends EventManagerPlugin {
+ // #docregion supports
+ supports(event: string): boolean {
+ return event.endsWith('.stop');
+ }
+ // #enddocregion supports
+
+ // #docregion addEventListener
+ addEventListener(element: HTMLElement, eventName: string, handler: (event: Event) => void): Function {
+ const eventWrapper = (event: Event) => {
+ event.stopPropagation();
+ handler(event);
+ };
+
+ // striping the stop modifier from the event name;
+ const nonModifiedEventName = eventName.replace(/\.stop$/, '');
+
+ return this.manager.addEventListener(element, nonModifiedEventName, eventWrapper);
+ }
+ // #enddocregion addEventListener
+}
diff --git a/aio/content/examples/custom-events/src/index.html b/aio/content/examples/custom-events/src/index.html
new file mode 100644
index 00000000000000..37a08c916952b7
--- /dev/null
+++ b/aio/content/examples/custom-events/src/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+ ContentProjection
+
+
+
+
+
+
+
+
diff --git a/aio/content/examples/custom-events/src/main.ts b/aio/content/examples/custom-events/src/main.ts
new file mode 100644
index 00000000000000..2ff02a03968ac0
--- /dev/null
+++ b/aio/content/examples/custom-events/src/main.ts
@@ -0,0 +1,12 @@
+import { bootstrapApplication, EVENT_MANAGER_PLUGINS } from '@angular/platform-browser';
+import { AppComponent } from './app/app.component';
+import { StopEventPlugin } from './app/stop-event-plugin';
+
+
+bootstrapApplication(AppComponent, {
+ // #docregion providers
+ providers: [
+ { provide: EVENT_MANAGER_PLUGINS, multi: true, useClass: StopEventPlugin}
+ ]
+ // #enddocregion providers
+});
diff --git a/aio/content/examples/custom-events/stackblitz.json b/aio/content/examples/custom-events/stackblitz.json
new file mode 100644
index 00000000000000..d4e56b1c165411
--- /dev/null
+++ b/aio/content/examples/custom-events/stackblitz.json
@@ -0,0 +1,10 @@
+{
+ "description": "Custom Events",
+ "files": [
+ "!**/*.d.ts",
+ "!**/*.js",
+ "!**/*.[1,2].*"
+ ],
+ "file": "src/app/app.component.ts",
+ "tags": ["Custom Events"]
+}
diff --git a/aio/content/examples/examples.bzl b/aio/content/examples/examples.bzl
index 2206c9e64fc57a..0b4d0483816a00 100644
--- a/aio/content/examples/examples.bzl
+++ b/aio/content/examples/examples.bzl
@@ -33,6 +33,7 @@ EXAMPLES = {
"component-overview": {"stackblitz": True, "zip": True},
"component-styles": {"stackblitz": True, "zip": True},
"content-projection": {"stackblitz": True, "zip": True},
+ "custom-events": {"stackblitz": True, "zip": True},
"dependency-injection": {"stackblitz": True, "zip": True},
"dependency-injection-in-action": {"stackblitz": True, "zip": True},
"deprecation-guide": {"stackblitz": True, "zip": True},
diff --git a/aio/content/guide/custom-events-management.md b/aio/content/guide/custom-events-management.md
new file mode 100644
index 00000000000000..fae2b99f698264
--- /dev/null
+++ b/aio/content/guide/custom-events-management.md
@@ -0,0 +1,57 @@
+# Custom Events Handeling
+
+Angular allows you to handle custom events, to extend existing ones or support new ones.
+
+## Handling user Events
+
+[Event Binding](/guide/event-binding) and the the `HostListener` decorator lets you listen for and respond to DOM events and components events.
+
+
+
+
+ <navbutton (click)="onClick($event)">You can click me<nav/button>
+
+
+## Event Handling by the EventManager
+
+Angular handles events using the `EventManager` service. It has a set of plugins that extend the `EventManagerPlugin` abstract class and delegates event subscription and handling to a plugin that supports particular events.
+
+Angular has a few built-in plugins such as one dedicated to [HammerJS](/api/platform-browser/HammerModule) events or the `KeyEventsPlugin` responsible for handling composite events such as `keydown.enter`.
+
+### `EventManagerPlugin`
+
+`EventManagerPlugin` is an abstract class with 2 methods to implement `supports` and `addEventListener`.
+
+Plugings are loaded using the `EVENT_MANAGER_PLUGINS` injection token and are provided to the `BrowserModule` using `multi: true`.
+
+For the `EventManager` to determine which plugin will handle the event, each plugin implements the `supports` method.
+The event manager will call `addEventListener` on the first plugin where `supports` returns `true`.
+
+## Handle a custom event
+
+Let's implement a plugin that will register events and call `Event.stopPropagation()` on them.
+This plugin will add the support for the `.stop` modifier and we'll be able to register events like `click.stop`.
+
+1. `support` needs to return `true` if the stop modifier is present :
+
+
+
+2. Wrap the event's handler to `stopPropagration` and register the wrapper.
+3. Extract the original event name and registrer the event with the manager.
+
+
+
+3. Add the plugin to the list of providers of the app.
+
+
+
+
+You can now listen to the new custom events via [Event bindings](/guide/event-binding) or the `HostListener` decorator.
+
+
+ <navbutton (click.stop)="onClick($event)">Propagation stopped<nav/button>
+
+
+
+
+Try this .
diff --git a/aio/content/guide/event-binding.md b/aio/content/guide/event-binding.md
index fabd52c722a93b..faa43c1aa55b74 100644
--- a/aio/content/guide/event-binding.md
+++ b/aio/content/guide/event-binding.md
@@ -87,5 +87,6 @@ For more information, visit the full reference for [key](https://developer.mozil
* [Property binding](guide/property-binding)
* [Text interpolation](guide/interpolation)
* [Two-way binding](guide/two-way-binding)
+* [Handle custom events](guide/custom-events-management)
@reviewed 2022-05-10
diff --git a/goldens/public-api/platform-browser/index.md b/goldens/public-api/platform-browser/index.md
index df3ccad791ab8b..fe700f876fd12f 100644
--- a/goldens/public-api/platform-browser/index.md
+++ b/goldens/public-api/platform-browser/index.md
@@ -92,6 +92,18 @@ export class EventManager {
static ɵprov: i0.ɵɵInjectableDeclaration;
}
+// @public
+export abstract class EventManagerPlugin {
+ abstract addEventListener(element: HTMLElement, eventName: string, handler: (event: Event) => void): Function;
+ // (undocumented)
+ manager: EventManager;
+ abstract supports(eventName: string): boolean;
+ // (undocumented)
+ static ɵfac: i0.ɵɵFactoryDeclaration;
+ // (undocumented)
+ static ɵprov: i0.ɵɵInjectableDeclaration;
+}
+
// @public
export const HAMMER_GESTURE_CONFIG: InjectionToken;
diff --git a/packages/core/test/render3/imported_renderer2.ts b/packages/core/test/render3/imported_renderer2.ts
index 4bb5312bede2d3..e8bc0a61b53d8b 100644
--- a/packages/core/test/render3/imported_renderer2.ts
+++ b/packages/core/test/render3/imported_renderer2.ts
@@ -17,10 +17,6 @@ import {EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event
import {isTextNode} from '@angular/platform-browser/testing/src/browser_util';
export class SimpleDomEventsPlugin extends EventManagerPlugin {
- constructor(doc: any) {
- super(doc);
- }
-
override supports(eventName: string): boolean {
return true;
}
@@ -38,7 +34,7 @@ export class SimpleDomEventsPlugin extends EventManagerPlugin {
export function getRendererFactory2(document: any): RendererFactory2 {
const fakeNgZone: NgZone = new NoopNgZone();
- const eventManager = new EventManager([new SimpleDomEventsPlugin(document)], fakeNgZone);
+ const eventManager = new EventManager([new SimpleDomEventsPlugin()], fakeNgZone);
const appId = 'appid';
const rendererFactory = new ɵDomRendererFactory2(
eventManager, new ɵSharedStylesHost(document, appId), appId, true, document,
diff --git a/packages/platform-browser/src/dom/events/dom_events.ts b/packages/platform-browser/src/dom/events/dom_events.ts
index e3826727e3dd55..5a3d82b2725240 100644
--- a/packages/platform-browser/src/dom/events/dom_events.ts
+++ b/packages/platform-browser/src/dom/events/dom_events.ts
@@ -6,24 +6,20 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {DOCUMENT} from '@angular/common';
-import {Inject, Injectable} from '@angular/core';
+import {Injectable} from '@angular/core';
import {EventManagerPlugin} from './event_manager';
@Injectable()
export class DomEventsPlugin extends EventManagerPlugin {
- constructor(@Inject(DOCUMENT) doc: any) {
- super(doc);
- }
-
// This plugin should come last in the list of plugins, because it accepts all
// events.
override supports(eventName: string): boolean {
return true;
}
- override addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
+ override addEventListener(
+ element: HTMLElement, eventName: string, handler: (event: Event) => void): () => void {
element.addEventListener(eventName, handler as EventListener, false);
return () => this.removeEventListener(element, eventName, handler as EventListener);
}
diff --git a/packages/platform-browser/src/dom/events/event_manager.ts b/packages/platform-browser/src/dom/events/event_manager.ts
index 1784364c688bc4..e81ad987058a51 100644
--- a/packages/platform-browser/src/dom/events/event_manager.ts
+++ b/packages/platform-browser/src/dom/events/event_manager.ts
@@ -9,7 +9,7 @@
import {Inject, Injectable, InjectionToken, NgZone} from '@angular/core';
/**
- * The injection token for the event-manager plug-in service.
+ * The injection token for plugins of the `EventManager` service.
*
* @publicApi
*/
@@ -48,7 +48,7 @@ export class EventManager {
*/
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
const plugin = this._findPluginFor(eventName);
- return plugin.addEventListener(element, eventName, handler);
+ return plugin.addEventListener(element, eventName, handler as (event: Event) => void);
}
/**
@@ -77,13 +77,24 @@ export class EventManager {
}
}
+/**
+ * The plugin definition for the `EventManager` class
+ *
+ * @publicApi
+ */
+@Injectable()
export abstract class EventManagerPlugin {
- constructor(private _doc: any) {}
-
// Using non-null assertion because it's set by EventManager's constructor
manager!: EventManager;
+ /**
+ * Should return `true` for every event name that should be supported by this plugin
+ */
abstract supports(eventName: string): boolean;
- abstract addEventListener(element: HTMLElement, eventName: string, handler: Function): Function;
+ /**
+ * Implement the behaviour for the supported events
+ */
+ abstract addEventListener(
+ element: HTMLElement, eventName: string, handler: (event: Event) => void): Function;
}
diff --git a/packages/platform-browser/src/dom/events/hammer_gestures.ts b/packages/platform-browser/src/dom/events/hammer_gestures.ts
index 8084b5b81d0917..dea53a193cca86 100644
--- a/packages/platform-browser/src/dom/events/hammer_gestures.ts
+++ b/packages/platform-browser/src/dom/events/hammer_gestures.ts
@@ -165,10 +165,9 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
private _loaderPromise: Promise|null = null;
constructor(
- @Inject(DOCUMENT) doc: any,
@Inject(HAMMER_GESTURE_CONFIG) private _config: HammerGestureConfig, private console: Console,
@Optional() @Inject(HAMMER_LOADER) private loader?: HammerLoader|null) {
- super(doc);
+ super();
}
override supports(eventName: string): boolean {
@@ -272,7 +271,7 @@ export class HammerGesturesPlugin extends EventManagerPlugin {
* HammerJS to detect gesture events.
*
* Note that applications still need to include the HammerJS script itself. This module
- * simply sets up the coordination layer between HammerJS and Angular's EventManager.
+ * simply sets up the coordination layer between HammerJS and Angular's `EventManager`.
*
* @publicApi
*/
diff --git a/packages/platform-browser/src/dom/events/key_events.ts b/packages/platform-browser/src/dom/events/key_events.ts
index f9810ab893d671..18b4196dc63c09 100644
--- a/packages/platform-browser/src/dom/events/key_events.ts
+++ b/packages/platform-browser/src/dom/events/key_events.ts
@@ -54,8 +54,9 @@ export class KeyEventsPlugin extends EventManagerPlugin {
* Initializes an instance of the browser plug-in.
* @param doc The document in which key events will be detected.
*/
+ // TODO: doc is unused and should be remove in the next major.
constructor(@Inject(DOCUMENT) doc: any) {
- super(doc);
+ super();
}
/**
diff --git a/packages/platform-browser/src/platform-browser.ts b/packages/platform-browser/src/platform-browser.ts
index 2efc7402599ea0..de878f7d926735 100644
--- a/packages/platform-browser/src/platform-browser.ts
+++ b/packages/platform-browser/src/platform-browser.ts
@@ -73,7 +73,7 @@ export {Title} from './browser/title';
export {disableDebugTools, enableDebugTools} from './browser/tools/tools';
export {By} from './dom/debug/by';
export {REMOVE_STYLES_ON_COMPONENT_DESTROY} from './dom/dom_renderer';
-export {EVENT_MANAGER_PLUGINS, EventManager} from './dom/events/event_manager';
+export {EVENT_MANAGER_PLUGINS, EventManager, EventManagerPlugin} from './dom/events/event_manager';
export {HAMMER_GESTURE_CONFIG, HAMMER_LOADER, HammerGestureConfig, HammerLoader, HammerModule} from './dom/events/hammer_gestures';
export {DomSanitizer, SafeHtml, SafeResourceUrl, SafeScript, SafeStyle, SafeUrl, SafeValue} from './security/dom_sanitization_service';
export {HydrationFeature, provideClientHydration, HydrationFeatureKind, withNoDomReuse, withNoHttpTransferCache} from './hydration';
diff --git a/packages/platform-browser/test/dom/events/event_manager_spec.ts b/packages/platform-browser/test/dom/events/event_manager_spec.ts
index 7f7aca8f06f0d1..16968a898c575f 100644
--- a/packages/platform-browser/test/dom/events/event_manager_spec.ts
+++ b/packages/platform-browser/test/dom/events/event_manager_spec.ts
@@ -23,14 +23,14 @@ describe('EventManager', () => {
beforeEach(() => {
doc = getDOM().supportsDOMEvents ? document : getDOM().createHtmlDocument();
zone = new NgZone({});
- domEventPlugin = new DomEventsPlugin(doc);
+ domEventPlugin = new DomEventsPlugin();
});
it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
() => {
const element = el('');
const handler = (e: any /** TODO #9100 */) => e;
- const plugin = new FakeEventManagerPlugin(doc, ['click']);
+ const plugin = new FakeEventManagerPlugin(['click']);
const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
manager.addEventListener(element, 'click', handler);
expect(plugin.eventHandler['click']).toBe(handler);
@@ -40,8 +40,8 @@ describe('EventManager', () => {
const element = el('');
const clickHandler = (e: any /** TODO #9100 */) => e;
const dblClickHandler = (e: any /** TODO #9100 */) => e;
- const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
- const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
+ const plugin1 = new FakeEventManagerPlugin(['dblclick']);
+ const plugin2 = new FakeEventManagerPlugin(['click', 'dblclick']);
const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
manager.addEventListener(element, 'click', clickHandler);
manager.addEventListener(element, 'dblclick', dblClickHandler);
@@ -51,7 +51,7 @@ describe('EventManager', () => {
it('should throw when no plugin can handle the event', () => {
const element = el('');
- const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
+ const plugin = new FakeEventManagerPlugin(['dblclick']);
const manager = new EventManager([plugin], new FakeNgZone());
expect(() => manager.addEventListener(element, 'click', null!))
.toThrowError('No event manager plugin found for event click');
@@ -326,7 +326,7 @@ describe('EventManager', () => {
(done: DoneFn) => {
doc = getDOM().supportsDOMEvents ? document : getDOM().createHtmlDocument();
zone = new NgZone({shouldCoalesceEventChangeDetection: true});
- domEventPlugin = new DomEventsPlugin(doc);
+ domEventPlugin = new DomEventsPlugin();
const element = el('');
const child = el('');
element.appendChild(child);
@@ -363,7 +363,7 @@ describe('EventManager', () => {
(done: DoneFn) => {
doc = getDOM().supportsDOMEvents ? document : getDOM().createHtmlDocument();
zone = new NgZone({shouldCoalesceRunChangeDetection: true});
- domEventPlugin = new DomEventsPlugin(doc);
+ domEventPlugin = new DomEventsPlugin();
const element = el('');
const child = el('');
element.appendChild(child);
@@ -400,7 +400,7 @@ describe('EventManager', () => {
(done: DoneFn) => {
doc = getDOM().supportsDOMEvents ? document : getDOM().createHtmlDocument();
zone = new NgZone({shouldCoalesceEventChangeDetection: true});
- domEventPlugin = new DomEventsPlugin(doc);
+ domEventPlugin = new DomEventsPlugin();
const element = el('');
const child = el('');
doc.body.appendChild(element);
@@ -446,7 +446,7 @@ describe('EventManager', () => {
(done: DoneFn) => {
doc = getDOM().supportsDOMEvents ? document : getDOM().createHtmlDocument();
zone = new NgZone({shouldCoalesceRunChangeDetection: true});
- domEventPlugin = new DomEventsPlugin(doc);
+ domEventPlugin = new DomEventsPlugin();
const element = el('');
const child = el('');
doc.body.appendChild(element);
@@ -494,8 +494,8 @@ describe('EventManager', () => {
class FakeEventManagerPlugin extends EventManagerPlugin {
eventHandler: {[event: string]: Function} = {};
- constructor(doc: any, public supportedEvents: string[]) {
- super(doc);
+ constructor(public supportedEvents: string[]) {
+ super();
}
override supports(eventName: string): boolean {
diff --git a/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts b/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts
index c319ea5ef3bb0a..4064fa4c662529 100644
--- a/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts
+++ b/packages/platform-browser/test/dom/events/hammer_gestures_spec.ts
@@ -27,7 +27,7 @@ import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-brow
describe('with no custom loader', () => {
beforeEach(() => {
- plugin = new HammerGesturesPlugin(document, new HammerGestureConfig(), fakeConsole);
+ plugin = new HammerGesturesPlugin(new HammerGestureConfig(), fakeConsole);
});
it('should warn user and do nothing when Hammer.js not loaded', () => {
@@ -88,7 +88,7 @@ import {HammerGestureConfig, HammerGesturesPlugin,} from '@angular/platform-brow
const hammerConfig = new HammerGestureConfig();
spyOn(hammerConfig, 'buildHammer').and.returnValue(fakeHammerInstance);
- plugin = new HammerGesturesPlugin(document, hammerConfig, fakeConsole, loader);
+ plugin = new HammerGesturesPlugin(hammerConfig, fakeConsole, loader);
// Use a fake EventManager that has access to the NgZone.
plugin.manager = {getZone: () => ngZone} as EventManager;
diff --git a/packages/platform-server/src/server_events.ts b/packages/platform-server/src/server_events.ts
index ea66012b0ed294..08eac783e5705a 100644
--- a/packages/platform-server/src/server_events.ts
+++ b/packages/platform-server/src/server_events.ts
@@ -6,19 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/common';
-import {Inject, Injectable} from '@angular/core';
+import {ɵgetDOM as getDOM} from '@angular/common';
+import {Injectable} from '@angular/core';
+import {EventManagerPlugin} from '@angular/platform-browser';
@Injectable()
-export class ServerEventManagerPlugin /* extends EventManagerPlugin which is private */ {
- constructor(@Inject(DOCUMENT) private doc: any) {}
-
+export class ServerEventManagerPlugin extends EventManagerPlugin {
// Handle all events on the server.
- supports(eventName: string) {
+ override supports(eventName: string) {
return true;
}
- addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
+ override addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
return getDOM().onAndCancel(element, eventName, handler);
}
}