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:
@ -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)) {
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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.'); }
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user