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

@ -17,7 +17,7 @@ import {Locals} from './parser/locals';
import {ChangeDetectionStrategy, ChangeDetectorState} from './constants';
import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile';
import {isObservable} from './observable_facade';
import {ObservableWrapper} from 'angular2/src/facade/async';
var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`);
@ -40,6 +40,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
mode: ChangeDetectionStrategy = null;
pipes: Pipes = null;
propertyBindingIndex: number;
outputSubscriptions: any[];
// This is an experimental feature. Works only in Dart.
subscriptions: any[];
@ -72,7 +73,7 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
handleEvent(eventName: string, elIndex: number, event: any): boolean {
if (!this.hydrated()) {
return true;
this.throwDehydratedError();
}
try {
var locals = new Map<string, any>();
@ -180,6 +181,8 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
this._unsubsribeFromObservables();
}
this._unsubscribeFromOutputs();
this.dispatcher = null;
this.context = null;
this.locals = null;
@ -258,6 +261,15 @@ export class AbstractChangeDetector<T> implements ChangeDetector {
}
}
private _unsubscribeFromOutputs(): void {
if (isPresent(this.outputSubscriptions)) {
for (var i = 0; i < this.outputSubscriptions.length; ++i) {
ObservableWrapper.dispose(this.outputSubscriptions[i]);
this.outputSubscriptions[i] = null;
}
}
}
// This is an experimental feature. Works only in Dart.
observeValue(value: any, index: number): any {
if (isObservable(value)) {

View File

@ -153,6 +153,7 @@ export class CodegenLogicUtil {
genHydrateDirectives(directiveRecords: DirectiveRecord[]): string {
var res = [];
var outputCount = 0;
for (var i = 0; i < directiveRecords.length; ++i) {
var r = directiveRecords[i];
var dirVarName = this._names.getDirectiveName(r.directiveIndex);
@ -160,14 +161,24 @@ export class CodegenLogicUtil {
if (isPresent(r.outputs)) {
r.outputs.forEach(output => {
var eventHandlerExpr = this._genEventHandler(r.directiveIndex.elementIndex, output[1]);
var statementStart =
`this.outputSubscriptions[${outputCount++}] = ${dirVarName}.${output[0]}`;
if (IS_DART) {
res.push(`${dirVarName}.${output[0]}.listen(${eventHandlerExpr});`);
res.push(`${statementStart}.listen(${eventHandlerExpr});`);
} else {
res.push(`${dirVarName}.${output[0]}.subscribe({next: ${eventHandlerExpr}});`);
res.push(`${statementStart}.subscribe({next: ${eventHandlerExpr}});`);
}
});
}
}
if (outputCount > 0) {
var statementStart = 'this.outputSubscriptions';
if (IS_DART) {
res.unshift(`${statementStart} = new List(${outputCount});`);
} else {
res.unshift(`${statementStart} = new Array(${outputCount});`);
}
}
return res.join("\n");
}

View File

@ -114,6 +114,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
super.observeDirective(this._getDirectiveFor(index), i);
}
}
this.outputSubscriptions = [];
for (var i = 0; i < this._directiveRecords.length; ++i) {
var r = this._directiveRecords[i];
if (isPresent(r.outputs)) {
@ -122,7 +123,8 @@ export class DynamicChangeDetector extends AbstractChangeDetector<any> {
<any>this._createEventHandler(r.directiveIndex.elementIndex, output[1]);
var directive = this._getDirectiveFor(r.directiveIndex);
var getter = reflector.getter(output[0]);
ObservableWrapper.subscribe(getter(directive), eventHandler);
this.outputSubscriptions.push(
ObservableWrapper.subscribe(getter(directive), eventHandler));
});
}
}

View File

@ -91,7 +91,7 @@ export class ChangeDetectionError extends WrappedException {
* This is an internal Angular error.
*/
export class DehydratedException extends BaseException {
constructor() { super('Attempt to detect changes on a dehydrated detector.'); }
constructor() { super('Attempt to use a dehydrated detector.'); }
}
/**