fix(core): always remove DOM listeners and stream subscriptions

This is needed to prevent memory leaks. The DOM
listeners don’t need to be removed for simple examples,
but a big internal app shows memory leaks because of them.

BREAKING CHANGE:
- `Renderer.listen` now has to return a function that
  removes the event listener.
This commit is contained in:
Tobias Bosch
2016-01-25 14:47:25 -08:00
parent 5f0baaac73
commit 0ae77753f3
15 changed files with 104 additions and 46 deletions

View File

@ -155,9 +155,9 @@ export class DomRenderer implements Renderer {
}
}
listen(renderElement: any, name: string, callback: Function) {
this._rootRenderer.eventManager.addEventListener(renderElement, name,
decoratePreventDefault(callback));
listen(renderElement: any, name: string, callback: Function): Function {
return this._rootRenderer.eventManager.addEventListener(renderElement, name,
decoratePreventDefault(callback));
}
listenGlobal(target: string, name: string, callback: Function): Function {

View File

@ -8,10 +8,11 @@ export class DomEventsPlugin extends EventManagerPlugin {
// events.
supports(eventName: string): boolean { return true; }
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var zone = this.manager.getZone();
var outsideHandler = (event) => zone.run(() => handler(event));
this.manager.getZone().runOutsideAngular(() => { DOM.on(element, eventName, outsideHandler); });
return this.manager.getZone().runOutsideAngular(
() => DOM.onAndCancel(element, eventName, outsideHandler));
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
@ -19,6 +20,6 @@ export class DomEventsPlugin extends EventManagerPlugin {
var zone = this.manager.getZone();
var outsideHandler = (event) => zone.run(() => handler(event));
return this.manager.getZone().runOutsideAngular(
() => { return DOM.onAndCancel(element, eventName, outsideHandler); });
() => DOM.onAndCancel(element, eventName, outsideHandler));
}
}

View File

@ -16,9 +16,9 @@ export class EventManager {
this._plugins = ListWrapper.reversed(plugins);
}
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var plugin = this._findPluginFor(eventName);
plugin.addEventListener(element, eventName, handler);
return plugin.addEventListener(element, eventName, handler);
}
addGlobalEventListener(target: string, eventName: string, handler: Function): Function {
@ -47,7 +47,7 @@ export class EventManagerPlugin {
// That is equivalent to having supporting $event.target
supports(eventName: string): boolean { return false; }
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
throw "not implemented";
}

View File

@ -15,17 +15,18 @@ export class HammerGesturesPlugin extends HammerGesturesPluginCommon {
return true;
}
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var zone = this.manager.getZone();
eventName = eventName.toLowerCase();
zone.runOutsideAngular(function() {
return zone.runOutsideAngular(function() {
// Creating the manager bind events, must be done outside of angular
var mc = new Hammer(element);
mc.get('pinch').set({enable: true});
mc.get('rotate').set({enable: true});
mc.on(eventName, function(eventObj) { zone.run(function() { handler(eventObj); }); });
var handler = function(eventObj) { zone.run(function() { handler(eventObj); }); };
mc.on(eventName, handler);
return () => { mc.off(eventName, handler); };
});
}
}

View File

@ -27,14 +27,15 @@ export class KeyEventsPlugin extends EventManagerPlugin {
return isPresent(KeyEventsPlugin.parseEventName(eventName));
}
addEventListener(element: HTMLElement, eventName: string, handler: Function) {
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
var parsedEvent = KeyEventsPlugin.parseEventName(eventName);
var outsideHandler = KeyEventsPlugin.eventCallback(
element, StringMapWrapper.get(parsedEvent, 'fullKey'), handler, this.manager.getZone());
this.manager.getZone().runOutsideAngular(() => {
DOM.on(element, StringMapWrapper.get(parsedEvent, 'domEventName'), outsideHandler);
return this.manager.getZone().runOutsideAngular(() => {
return DOM.onAndCancel(element, StringMapWrapper.get(parsedEvent, 'domEventName'),
outsideHandler);
});
}