From e8e430e630300fd5cfbcbdca1f3c9bfbbaa42d70 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Fri, 21 Aug 2015 14:45:38 -0700 Subject: [PATCH] feat(change_detection): added support for observable components and directives --- .../abstract_change_detector.ts | 60 +++++++-- .../change_detection_jit_generator.ts | 15 +-- .../change_detection/codegen_logic_util.ts | 26 +++- .../src/change_detection/codegen_name_util.ts | 4 - .../src/change_detection/constants.ts | 8 +- .../dynamic_change_detector.ts | 19 ++- .../change_detection/proto_change_detector.ts | 3 +- .../change_detector_codegen.dart | 23 +--- .../change_detector_config.ts | 21 ++- .../change_detection/change_detector_spec.ts | 126 +++++++++++------- .../expected/bar.ng_deps.dart | 2 +- 11 files changed, 202 insertions(+), 105 deletions(-) diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts index 7e3b786bdf..c51c1a0250 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/change_detection/abstract_change_detector.ts @@ -15,6 +15,8 @@ import {Locals} from './parser/locals'; import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; import {isObservable} from './observable_facade'; +import {ON_PUSH_OBSERVE} from './constants'; + var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`); @@ -44,7 +46,7 @@ export class AbstractChangeDetector implements ChangeDetector { constructor(public id: string, public dispatcher: ChangeDispatcher, public numberOfPropertyProtoRecords: number, public bindingTargets: BindingTarget[], - public directiveIndices: DirectiveIndex[], public modeOnHydrate: string) { + public directiveIndices: DirectiveIndex[], public strategy: string) { this.ref = new ChangeDetectorRef(this); } @@ -116,8 +118,13 @@ export class AbstractChangeDetector implements ChangeDetector { // This method is not intended to be overridden. Subclasses should instead provide an // implementation of `hydrateDirectives`. hydrate(context: T, locals: Locals, directives: any, pipes: any): void { - this.mode = this.modeOnHydrate; + this.mode = ChangeDetectionUtil.changeDetectionMode(this.strategy); this.context = context; + + if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) { + this.observeComponent(context); + } + this.locals = locals; this.pipes = pipes; this.hydrateDirectives(directives); @@ -133,7 +140,9 @@ export class AbstractChangeDetector implements ChangeDetector { this.dehydrateDirectives(true); // This is an experimental feature. Works only in Dart. - this.unsubsribeFromObservables(); + if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) { + this._unsubsribeFromObservables(); + } this.context = null; this.locals = null; @@ -172,7 +181,8 @@ export class AbstractChangeDetector implements ChangeDetector { } } - private unsubsribeFromObservables(): void { + // This is an experimental feature. Works only in Dart. + private _unsubsribeFromObservables(): void { if (isPresent(this.subscriptions)) { for (var i = 0; i < this.subscriptions.length; ++i) { var s = this.subscriptions[i]; @@ -185,12 +195,9 @@ export class AbstractChangeDetector implements ChangeDetector { } // This is an experimental feature. Works only in Dart. - protected observe(value: any, index: number): any { + protected observeValue(value: any, index: number): any { if (isObservable(value)) { - if (isBlank(this.subscriptions)) { - this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1); - this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + 1); - } + this._createArrayToStoreObservables(); if (isBlank(this.subscriptions[index])) { this.streams[index] = value.changes; this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck()); @@ -203,6 +210,41 @@ export class AbstractChangeDetector implements ChangeDetector { return value; } + // This is an experimental feature. Works only in Dart. + protected observeDirective(value: any, index: number): any { + if (isObservable(value)) { + this._createArrayToStoreObservables(); + var arrayIndex = this.numberOfPropertyProtoRecords + index + 2; // +1 is component + this.streams[arrayIndex] = value.changes; + this.subscriptions[arrayIndex] = value.changes.listen((_) => this.ref.requestCheck()); + } + return value; + } + + // This is an experimental feature. Works only in Dart. + protected observeComponent(value: any): any { + if (isObservable(value)) { + this._createArrayToStoreObservables(); + var index = this.numberOfPropertyProtoRecords + 1; + this.streams[index] = value.changes; + this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck()); + } + return value; + } + + private _createArrayToStoreObservables(): void { + if (isBlank(this.subscriptions)) { + this.subscriptions = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + + this.directiveIndices.length + 2); + this.streams = ListWrapper.createFixedSize(this.numberOfPropertyProtoRecords + + this.directiveIndices.length + 2); + } + } + + protected getDirectiveFor(directives: any, index: number): any { + return directives.getDirectiveFor(this.directiveIndices[index]); + } + protected getDetectorFor(directives: any, index: number): ChangeDetector { return directives.getDetectorFor(this.directiveIndices[index]); } diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.ts b/modules/angular2/src/change_detection/change_detection_jit_generator.ts index 02d533e610..aac2740549 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -8,6 +8,7 @@ import {DirectiveIndex, DirectiveRecord} from './directive_record'; import {ProtoRecord, RecordType} from './proto_record'; import {CodegenNameUtil, sanitizeName} from './codegen_name_util'; import {CodegenLogicUtil} from './codegen_logic_util'; +import {codify} from './codegen_facade'; import {EventBinding} from './event_binding'; import {BindingTarget} from './binding_record'; import {ChangeDetectorGenConfig} from './interfaces'; @@ -48,7 +49,7 @@ export class ChangeDetectorJITGenerator { ${ABSTRACT_CHANGE_DETECTOR}.call( this, ${JSON.stringify(this.id)}, dispatcher, ${this.records.length}, ${this._typeName}.gen_propertyBindingTargets, ${this._typeName}.gen_directiveIndices, - "${ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy)}"); + ${codify(this.changeDetectionStrategy)}); this.dehydrateDirectives(false); } @@ -160,7 +161,7 @@ export class ChangeDetectorJITGenerator { } _maybeGenHydrateDirectives(): string { - var hydrateDirectivesCode = this._genHydrateDirectives(); + var hydrateDirectivesCode = this._logic.genHydrateDirectives(this.directiveRecords); var hydrateDetectorsCode = this._logic.genHydrateDetectors(this.directiveRecords); if (!hydrateDirectivesCode && !hydrateDetectorsCode) return ''; return `${this._typeName}.prototype.hydrateDirectives = function(directives) { @@ -169,16 +170,6 @@ export class ChangeDetectorJITGenerator { }`; } - _genHydrateDirectives(): string { - var directiveFieldNames = this._names.getAllDirectiveNames(); - var lines = ListWrapper.createFixedSize(directiveFieldNames.length); - for (var i = 0, iLen = directiveFieldNames.length; i < iLen; ++i) { - lines[i] = `${directiveFieldNames[i]} = directives.getDirectiveFor( - ${this._names.getDirectivesAccessorName()}[${i}]);`; - } - return lines.join('\n'); - } - _maybeGenCallOnAllChangesDone(): string { var notifications = []; var dirs = this.directiveRecords; diff --git a/modules/angular2/src/change_detection/codegen_logic_util.ts b/modules/angular2/src/change_detection/codegen_logic_util.ts index 26b7506dbb..83690713e4 100644 --- a/modules/angular2/src/change_detection/codegen_logic_util.ts +++ b/modules/angular2/src/change_detection/codegen_logic_util.ts @@ -5,11 +5,7 @@ import {codify, combineGeneratedStrings, rawString} from './codegen_facade'; import {ProtoRecord, RecordType} from './proto_record'; import {BindingTarget} from './binding_record'; import {DirectiveRecord} from './directive_record'; - -/** - * This is an experimental feature. Works only in Dart. - */ -const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE"; +import {ON_PUSH_OBSERVE} from './constants'; /** * Class responsible for providing change detection logic for chagne detector classes. @@ -118,7 +114,7 @@ export class CodegenLogicUtil { _observe(exp: string, rec: ProtoRecord): string { // This is an experimental feature. Works only in Dart. if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) { - return `this.observe(${exp}, ${rec.selfIndex})`; + return `this.observeValue(${exp}, ${rec.selfIndex})`; } else { return exp; } @@ -152,6 +148,24 @@ export class CodegenLogicUtil { return combineGeneratedStrings(iVals); } + genHydrateDirectives(directiveRecords: DirectiveRecord[]): string { + var res = []; + for (var i = 0; i < directiveRecords.length; ++i) { + var r = directiveRecords[i]; + res.push(`${this._names.getDirectiveName(r.directiveIndex)} = ${this._genReadDirective(i)};`); + } + return res.join("\n"); + } + + private _genReadDirective(index: number) { + // This is an experimental feature. Works only in Dart. + if (StringWrapper.equals(this._changeDetection, ON_PUSH_OBSERVE)) { + return `this.observeDirective(this.getDirectiveFor(directives, ${index}), ${index})`; + } else { + return `this.getDirectiveFor(directives, ${index})`; + } + } + genHydrateDetectors(directiveRecords: DirectiveRecord[]): string { var res = []; for (var i = 0; i < directiveRecords.length; ++i) { diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index 53694a0dc8..6d86e3894c 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -190,10 +190,6 @@ export class CodegenNameUtil { return this._addFieldPrefix(`${this._sanitizedNames[idx]}_pipe`); } - getAllDirectiveNames(): List { - return ListWrapper.map(this.directiveRecords, d => this.getDirectiveName(d.directiveIndex)); - } - getDirectiveName(d: DirectiveIndex): string { return this._addFieldPrefix(`directive_${d.name}`); } diff --git a/modules/angular2/src/change_detection/constants.ts b/modules/angular2/src/change_detection/constants.ts index c481ebc8eb..ff7435b4c5 100644 --- a/modules/angular2/src/change_detection/constants.ts +++ b/modules/angular2/src/change_detection/constants.ts @@ -37,4 +37,10 @@ export const DEFAULT: string = "DEFAULT"; export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean { return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT); -} \ No newline at end of file +} + + +/** + * This is an experimental feature. Works only in Dart. + */ +export const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE"; \ No newline at end of file diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.ts b/modules/angular2/src/change_detection/dynamic_change_detector.ts index 035cc6a874..7b30b4a67f 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -14,7 +14,7 @@ import {DirectiveRecord, DirectiveIndex} from './directive_record'; import {Locals} from './parser/locals'; import {ChangeDetectorGenConfig} from './interfaces'; import {ChangeDetectionUtil, SimpleChange} from './change_detection_util'; - +import {ON_PUSH_OBSERVE} from './constants'; import {ProtoRecord, RecordType} from './proto_record'; export class DynamicChangeDetector extends AbstractChangeDetector { @@ -26,11 +26,11 @@ export class DynamicChangeDetector extends AbstractChangeDetector { constructor(id: string, dispatcher: any, numberOfPropertyProtoRecords: number, propertyBindingTargets: BindingTarget[], directiveIndices: DirectiveIndex[], - modeOnHydrate: string, private records: ProtoRecord[], + strategy: string, private records: ProtoRecord[], private eventBindings: EventBinding[], private directiveRecords: DirectiveRecord[], private genConfig: ChangeDetectorGenConfig) { super(id, dispatcher, numberOfPropertyProtoRecords, propertyBindingTargets, directiveIndices, - modeOnHydrate); + strategy); var len = records.length + 1; this.values = ListWrapper.createFixedSize(len); this.localPipes = ListWrapper.createFixedSize(len); @@ -87,6 +87,13 @@ export class DynamicChangeDetector extends AbstractChangeDetector { hydrateDirectives(directives: any): void { this.values[0] = this.context; this.directives = directives; + + if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) { + for (var i = 0; i < this.directiveIndices.length; ++i) { + var index = this.directiveIndices[i]; + super.observeDirective(directives.getDirectiveFor(index), i); + } + } } dehydrateDirectives(destroyPipes: boolean) { @@ -211,7 +218,11 @@ export class DynamicChangeDetector extends AbstractChangeDetector { return null; } - var currValue = this.observe(this._calculateCurrValue(proto, values, locals), proto.selfIndex); + var currValue = this._calculateCurrValue(proto, values, locals); + if (StringWrapper.equals(this.strategy, ON_PUSH_OBSERVE)) { + super.observeValue(currValue, proto.selfIndex); + } + if (proto.shouldBeChecked()) { var prevValue = this._readSelf(proto, values); if (!isSame(prevValue, currValue)) { diff --git a/modules/angular2/src/change_detection/proto_change_detector.ts b/modules/angular2/src/change_detection/proto_change_detector.ts index 0e3136ad60..e9f08a8293 100644 --- a/modules/angular2/src/change_detection/proto_change_detector.ts +++ b/modules/angular2/src/change_detection/proto_change_detector.ts @@ -52,8 +52,7 @@ export class DynamicProtoChangeDetector implements ProtoChangeDetector { instantiate(dispatcher: any): ChangeDetector { return new DynamicChangeDetector( this.definition.id, dispatcher, this._propertyBindingRecords.length, - this._propertyBindingTargets, this._directiveIndices, - ChangeDetectionUtil.changeDetectionMode(this.definition.strategy), + this._propertyBindingTargets, this._directiveIndices, this.definition.strategy, this._propertyBindingRecords, this._eventBindingRecords, this.definition.directiveRecords, this.definition.genConfig); } diff --git a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart index ca33c8aa28..25a78bff2e 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -10,6 +10,7 @@ import 'package:angular2/src/change_detection/proto_change_detector.dart'; import 'package:angular2/src/change_detection/proto_record.dart'; import 'package:angular2/src/change_detection/event_binding.dart'; import 'package:angular2/src/change_detection/binding_record.dart'; +import 'package:angular2/src/change_detection/codegen_facade.dart' show codify; import 'package:angular2/src/facade/lang.dart' show BaseException; /// Responsible for generating change detector classes for Angular 2. @@ -74,7 +75,7 @@ class _CodegenState { /// The name of the generated change detector class. This is an implementation /// detail and should not be visible to users. final String _changeDetectorTypeName; - final String _changeDetectionMode; + final String _changeDetectionStrategy; final List _directiveRecords; final List _records; final List _eventBindings; @@ -87,16 +88,14 @@ class _CodegenState { this._changeDetectorDefId, this._contextTypeName, this._changeDetectorTypeName, - String changeDetectionStrategy, + this._changeDetectionStrategy, this._records, this._propertyBindingTargets, this._eventBindings, this._directiveRecords, this._logic, this._names, - this._genConfig) - : _changeDetectionMode = - ChangeDetectionUtil.changeDetectionMode(changeDetectionStrategy); + this._genConfig); factory _CodegenState(String typeName, String changeDetectorTypeName, ChangeDetectorDefinition def) { @@ -130,7 +129,7 @@ class _CodegenState { dispatcher, ${_records.length}, ${_changeDetectorTypeName}.gen_propertyBindingTargets, ${_changeDetectorTypeName}.gen_directiveIndices, - '$_changeDetectionMode') { + ${codify(_changeDetectionStrategy)}) { dehydrateDirectives(false); } @@ -248,7 +247,7 @@ class _CodegenState { } String _maybeGenHydrateDirectives() { - var hydrateDirectivesCode = _genHydrateDirectives(); + var hydrateDirectivesCode = _logic.genHydrateDirectives(_directiveRecords); var hydrateDetectorsCode = _logic.genHydrateDetectors(_directiveRecords); if (hydrateDirectivesCode.isEmpty && hydrateDetectorsCode.isEmpty) { return ''; @@ -257,16 +256,6 @@ class _CodegenState { '{ $hydrateDirectivesCode $hydrateDetectorsCode }'; } - String _genHydrateDirectives() { - var buf = new StringBuffer(); - var directiveFieldNames = _names.getAllDirectiveNames(); - for (var i = 0; i < directiveFieldNames.length; ++i) { - buf.writeln('${directiveFieldNames[i]} = directives.getDirectiveFor(' - '${_names.getDirectivesAccessorName()}[$i]);'); - } - return '$buf'; - } - /// Generates calls to `onAllChangesDone` for all `Directive`s that request /// them. String _maybeGenCallOnAllChangesDone() { diff --git a/modules/angular2/test/change_detection/change_detector_config.ts b/modules/angular2/test/change_detection/change_detector_config.ts index eab0fa6d3b..291e96d56f 100644 --- a/modules/angular2/test/change_detection/change_detector_config.ts +++ b/modules/angular2/test/change_detection/change_detector_config.ts @@ -12,6 +12,7 @@ import { Parser, ChangeDetectorGenConfig } from 'angular2/src/change_detection/change_detection'; +import {ON_PUSH_OBSERVE} from 'angular2/src/change_detection/constants'; import {reflector} from 'angular2/src/reflection/reflection'; import {ReflectionCapabilities} from 'angular2/src/reflection/reflection_capabilities'; @@ -106,11 +107,19 @@ export function getDefinition(id: string): TestDefinition { [_DirectiveUpdating.basicRecords[0]], genConfig); testDef = new TestDefinition(id, cdDef, null); - } else if (id == "onPushObserve") { + } else if (id == "onPushObserveBinding") { var records = _createBindingRecords("a"); - let cdDef = new ChangeDetectorDefinition(id, "ON_PUSH_OBSERVE", [], records, [], [], genConfig); + let cdDef = new ChangeDetectorDefinition(id, ON_PUSH_OBSERVE, [], records, [], [], genConfig); testDef = new TestDefinition(id, cdDef, null); + } else if (id == "onPushObserveComponent") { + let cdDef = new ChangeDetectorDefinition(id, ON_PUSH_OBSERVE, [], [], [], [], genConfig); + testDef = new TestDefinition(id, cdDef, null); + + } else if (id == "onPushObserveDirective") { + let cdDef = new ChangeDetectorDefinition(id, ON_PUSH_OBSERVE, [], [], [], + [_DirectiveUpdating.recordNoCallbacks], genConfig); + testDef = new TestDefinition(id, cdDef, null); } else if (id == "updateElementProduction") { var genConfig = new ChangeDetectorGenConfig(false, false, false); var records = _createBindingRecords("name"); @@ -118,7 +127,6 @@ export function getDefinition(id: string): TestDefinition { testDef = new TestDefinition(id, cdDef, null); } - if (isBlank(testDef)) { throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`; } @@ -144,7 +152,12 @@ export function getAllDefinitions(): List { ListWrapper.concat(allDefs, StringMapWrapper.keys(_DirectiveUpdating.availableDefinitions)); allDefs = ListWrapper.concat(allDefs, _availableEventDefinitions); allDefs = ListWrapper.concat(allDefs, _availableHostEventDefinitions); - allDefs = ListWrapper.concat(allDefs, ["onPushObserve", "updateElementProduction"]); + allDefs = ListWrapper.concat(allDefs, [ + "onPushObserveBinding", + "onPushObserveComponent", + "onPushObserveDirective", + "updateElementProduction" + ]); return ListWrapper.map(allDefs, (id) => getDefinition(id)); } diff --git a/modules/angular2/test/change_detection/change_detector_spec.ts b/modules/angular2/test/change_detection/change_detector_spec.ts index 86dea2eece..2cacb40255 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -781,64 +781,100 @@ export function main() { }); if (IS_DART) { - it('should mark ON_PUSH_OBSERVE detectors as CHECK_ONCE when an observable fires an event', - fakeAsync(() => { - var context = new TestDirective(); - context.a = createObservableModel(); + describe('ON_PUSH_OBSERVE', () => { + it('should mark ON_PUSH_OBSERVE detectors as CHECK_ONCE when an observable fires an event', + fakeAsync(() => { + var context = new TestDirective(); + context.a = createObservableModel(); - var cd = _createWithoutHydrate('onPushObserve').changeDetector; - cd.hydrate(context, null, directives, null); - cd.detectChanges(); + var cd = _createWithoutHydrate('onPushObserveBinding').changeDetector; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); - expect(cd.mode).toEqual(CHECKED); + expect(cd.mode).toEqual(CHECKED); - context.a.pushUpdate(); - tick(); + context.a.pushUpdate(); + tick(); - expect(cd.mode).toEqual(CHECK_ONCE); - })); + expect(cd.mode).toEqual(CHECK_ONCE); + })); - it('should unsubscribe from an old observable when an object changes', fakeAsync(() => { - var originalModel = createObservableModel(); - var context = new TestDirective(); - context.a = originalModel; + it('should mark ON_PUSH_OBSERVE detectors as CHECK_ONCE when an observable context fires an event', + fakeAsync(() => { + var context = createObservableModel(); - var cd = _createWithoutHydrate('onPushObserve').changeDetector; - cd.hydrate(context, null, directives, null); - cd.detectChanges(); + var cd = _createWithoutHydrate('onPushObserveComponent').changeDetector; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); - context.a = createObservableModel(); - cd.mode = CHECK_ONCE; - cd.detectChanges(); + expect(cd.mode).toEqual(CHECKED); - // Updating this model will not reenable the detector. This model is not longer - // used. - originalModel.pushUpdate(); - tick(); - expect(cd.mode).toEqual(CHECKED); - })); + context.pushUpdate(); + tick(); - it('should unsubscribe from observables when dehydrating', fakeAsync(() => { - var originalModel = createObservableModel(); - var context = new TestDirective(); - context.a = originalModel; + expect(cd.mode).toEqual(CHECK_ONCE); + })); - var cd = _createWithoutHydrate('onPushObserve').changeDetector; - cd.hydrate(context, null, directives, null); - cd.detectChanges(); + it('should mark ON_PUSH_OBSERVE detectors as CHECK_ONCE when an observable directive fires an event', + fakeAsync(() => { + var dir = createObservableModel(); + var directives = new FakeDirectives([dir], []); - cd.dehydrate(); + var cd = _createWithoutHydrate('onPushObserveDirective').changeDetector; + cd.hydrate(_DEFAULT_CONTEXT, null, directives, null); + cd.detectChanges(); - context.a = "not an observable model"; - cd.hydrate(context, null, directives, null); - cd.detectChanges(); + expect(cd.mode).toEqual(CHECKED); - // Updating this model will not reenable the detector. This model is not longer - // used. - originalModel.pushUpdate(); - tick(); - expect(cd.mode).toEqual(CHECKED); - })); + dir.pushUpdate(); + tick(); + + expect(cd.mode).toEqual(CHECK_ONCE); + })); + + it('should unsubscribe from an old observable when an object changes', + fakeAsync(() => { + var originalModel = createObservableModel(); + var context = new TestDirective(); + context.a = originalModel; + + var cd = _createWithoutHydrate('onPushObserveBinding').changeDetector; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); + + context.a = createObservableModel(); + cd.mode = CHECK_ONCE; + cd.detectChanges(); + + // Updating this model will not reenable the detector. This model is not longer + // used. + originalModel.pushUpdate(); + tick(); + expect(cd.mode).toEqual(CHECKED); + })); + + it('should unsubscribe from observables when dehydrating', fakeAsync(() => { + var originalModel = createObservableModel(); + var context = new TestDirective(); + context.a = originalModel; + + var cd = _createWithoutHydrate('onPushObserveBinding').changeDetector; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); + + cd.dehydrate(); + + context.a = "not an observable model"; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); + + // Updating this model will not reenable the detector. This model is not longer + // used. + originalModel.pushUpdate(); + tick(); + expect(cd.mode).toEqual(CHECKED); + })); + }); } }); }); diff --git a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart index 2f47337566..de351bfc19 100644 --- a/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart +++ b/modules/angular2/test/transform/integration/two_annotations_files/expected/bar.ng_deps.dart @@ -29,7 +29,7 @@ class _MyComponent_ChangeDetector0 _MyComponent_ChangeDetector0(dispatcher) : super( "MyComponent_comp_0", dispatcher, 2, _MyComponent_ChangeDetector0.gen_propertyBindingTargets, - _MyComponent_ChangeDetector0.gen_directiveIndices, 'ALWAYS_CHECK') { + _MyComponent_ChangeDetector0.gen_directiveIndices,null) { dehydrateDirectives(false); }