diff --git a/modules/angular2/src/change_detection/abstract_change_detector.ts b/modules/angular2/src/change_detection/abstract_change_detector.ts index be535ca395..aac85baf53 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.ts +++ b/modules/angular2/src/change_detection/abstract_change_detector.ts @@ -1,4 +1,4 @@ -import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; +import {isPresent, isBlank, BaseException, StringWrapper} from 'angular2/src/facade/lang'; import {List, ListWrapper} from 'angular2/src/facade/collection'; import {ChangeDetectionUtil} from './change_detection_util'; import {ChangeDetectorRef} from './change_detector_ref'; @@ -13,8 +13,9 @@ import { import {ProtoRecord} from './proto_record'; import {BindingRecord} from './binding_record'; import {Locals} from './parser/locals'; -import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; +import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './constants'; import {wtfCreateScope, wtfLeave, WtfScopeFn} from '../profile/profile'; +import {isObservable} from './observable_facade'; var _scope_check: WtfScopeFn = wtfCreateScope(`ChangeDetector#check(ascii id, bool throwOnChange)`); @@ -42,6 +43,10 @@ export class AbstractChangeDetector implements ChangeDetector { firstProtoInCurrentBinding: number; protos: List; + // This is an experimental feature. Works only in Dart. + subscriptions: any[]; + streams: any[]; + constructor(public id: string, dispatcher: ChangeDispatcher, protos: List, directiveRecords: List, public modeOnHydrate: string) { this.ref = new ChangeDetectorRef(this); @@ -79,13 +84,14 @@ export class AbstractChangeDetector implements ChangeDetector { checkNoChanges(): void { throw new BaseException("Not implemented"); } runDetectChanges(throwOnChange: boolean): void { - if (this.mode === DETACHED || this.mode === CHECKED) return; + if (StringWrapper.equals(this.mode, DETACHED) || StringWrapper.equals(this.mode, CHECKED)) + return; var s = _scope_check(this.id, throwOnChange); this.detectChangesInRecords(throwOnChange); this._detectChangesInLightDomChildren(throwOnChange); if (throwOnChange === false) this.callOnAllChangesDone(); this._detectChangesInShadowDomChildren(throwOnChange); - if (this.mode === CHECK_ONCE) this.mode = CHECKED; + if (StringWrapper.equals(this.mode, CHECK_ONCE)) this.mode = CHECKED; wtfLeave(s); } @@ -132,6 +138,10 @@ export class AbstractChangeDetector implements ChangeDetector { // implementation of `dehydrateDirectives`. dehydrate(): void { this.dehydrateDirectives(true); + + // This is an experimental feature. Works only in Dart. + this.unsubsribeFromObservables(); + this.context = null; this.locals = null; this.pipes = null; @@ -163,12 +173,43 @@ export class AbstractChangeDetector implements ChangeDetector { markPathToRootAsCheckOnce(): void { var c: ChangeDetector = this; - while (isPresent(c) && c.mode != DETACHED) { - if (c.mode === CHECKED) c.mode = CHECK_ONCE; + while (isPresent(c) && !StringWrapper.equals(c.mode, DETACHED)) { + if (StringWrapper.equals(c.mode, CHECKED)) c.mode = CHECK_ONCE; c = c.parent; } } + private unsubsribeFromObservables(): void { + if (isPresent(this.subscriptions)) { + for (var i = 0; i < this.subscriptions.length; ++i) { + var s = this.subscriptions[i]; + if (isPresent(this.subscriptions[i])) { + s.cancel(); + this.subscriptions[i] = null; + } + } + } + } + + // This is an experimental feature. Works only in Dart. + protected observe(value: any, index: number): any { + if (isObservable(value)) { + if (isBlank(this.subscriptions)) { + this.subscriptions = ListWrapper.createFixedSize(this.protos.length + 1); + this.streams = ListWrapper.createFixedSize(this.protos.length + 1); + } + if (isBlank(this.subscriptions[index])) { + this.streams[index] = value.changes; + this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck()); + } else if (this.streams[index] !== value.changes) { + this.subscriptions[index].cancel(); + this.streams[index] = value.changes; + this.subscriptions[index] = value.changes.listen((_) => this.ref.requestCheck()); + } + } + return value; + } + protected notifyDispatcher(value: any): void { this.dispatcher.notifyOnBinding(this._currentBinding(), value); } diff --git a/modules/angular2/src/change_detection/binding_record.ts b/modules/angular2/src/change_detection/binding_record.ts index 44f2d8275c..b92fd4312e 100644 --- a/modules/angular2/src/change_detection/binding_record.ts +++ b/modules/angular2/src/change_detection/binding_record.ts @@ -23,8 +23,8 @@ export class BindingRecord { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; } - isOnPushChangeDetection(): boolean { - return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection(); + isDefaultChangeDetection(): boolean { + return isBlank(this.directiveRecord) || this.directiveRecord.isDefaultChangeDetection(); } isDirective(): boolean { return this.mode === DIRECTIVE; } 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 4935098f4c..ba26018123 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.ts +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.ts @@ -35,7 +35,7 @@ export class ChangeDetectorJITGenerator { public directiveRecords: List, private generateCheckNoChanges: boolean) { this._names = new CodegenNameUtil(this.records, this.eventBindings, this.directiveRecords, UTIL); - this._logic = new CodegenLogicUtil(this._names, UTIL); + this._logic = new CodegenLogicUtil(this._names, UTIL, changeDetectionStrategy); this._typeName = sanitizeName(`ChangeDetector_${this.id}`); } @@ -116,10 +116,10 @@ export class ChangeDetectorJITGenerator { _genMarkPathToRootAsCheckOnce(r: ProtoRecord): string { var br = r.bindingRecord; - if (br.isOnPushChangeDetection()) { - return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`; - } else { + if (br.isDefaultChangeDetection()) { return ""; + } else { + return `${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();`; } } @@ -369,7 +369,7 @@ export class ChangeDetectorJITGenerator { _genNotifyOnPushDetectors(r: ProtoRecord): string { var br = r.bindingRecord; - if (!r.lastInDirective || !br.isOnPushChangeDetection()) return ""; + if (!r.lastInDirective || br.isDefaultChangeDetection()) return ""; var retVal = ` if(${IS_CHANGED_LOCAL}) { ${this._names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce(); diff --git a/modules/angular2/src/change_detection/change_detection_util.ts b/modules/angular2/src/change_detection/change_detection_util.ts index 3bf63216c5..f05faa6457 100644 --- a/modules/angular2/src/change_detection/change_detection_util.ts +++ b/modules/angular2/src/change_detection/change_detection_util.ts @@ -1,7 +1,20 @@ -import {CONST_EXPR, isPresent, isBlank, BaseException, Type} from 'angular2/src/facade/lang'; +import { + CONST_EXPR, + isPresent, + isBlank, + BaseException, + Type, + StringWrapper +} from 'angular2/src/facade/lang'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {ProtoRecord} from './proto_record'; -import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants'; +import { + CHECK_ALWAYS, + CHECK_ONCE, + CHECKED, + DETACHED, + isDefaultChangeDetectionStrategy +} from './constants'; import {implementsOnDestroy} from './pipe_lifecycle_reflector'; @@ -166,7 +179,7 @@ export class ChangeDetectionUtil { } static changeDetectionMode(strategy: string): string { - return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS; + return isDefaultChangeDetectionStrategy(strategy) ? CHECK_ALWAYS : CHECK_ONCE; } static simpleChange(previousValue: any, currentValue: any): SimpleChange { diff --git a/modules/angular2/src/change_detection/codegen_logic_util.ts b/modules/angular2/src/change_detection/codegen_logic_util.ts index 57f7b0dd16..13842ee596 100644 --- a/modules/angular2/src/change_detection/codegen_logic_util.ts +++ b/modules/angular2/src/change_detection/codegen_logic_util.ts @@ -1,14 +1,20 @@ import {ListWrapper} from 'angular2/src/facade/collection'; -import {BaseException, Json} from 'angular2/src/facade/lang'; +import {BaseException, Json, StringWrapper} from 'angular2/src/facade/lang'; import {CodegenNameUtil} from './codegen_name_util'; import {codify, combineGeneratedStrings, rawString} from './codegen_facade'; import {ProtoRecord, RecordType} from './proto_record'; +/** + * This is an experimental feature. Works only in Dart. + */ +const ON_PUSH_OBSERVE = "ON_PUSH_OBSERVE"; + /** * Class responsible for providing change detection logic for chagne detector classes. */ export class CodegenLogicUtil { - constructor(private _names: CodegenNameUtil, private _utilName: string) {} + constructor(private _names: CodegenNameUtil, private _utilName: string, + private _changeDetection: string) {} /** * Generates a statement which updates the local variable representing `protoRec` with the current @@ -46,11 +52,13 @@ export class CodegenLogicUtil { break; case RecordType.PROPERTY_READ: - rhs = `${context}.${protoRec.name}`; + rhs = this._observe(`${context}.${protoRec.name}`, protoRec); break; case RecordType.SAFE_PROPERTY: - rhs = `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}`; + var read = this._observe(`${context}.${protoRec.name}`, protoRec); + rhs = + `${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(read, protoRec)}`; break; case RecordType.PROPERTY_WRITE: @@ -58,16 +66,17 @@ export class CodegenLogicUtil { break; case RecordType.LOCAL: - rhs = `${localsAccessor}.get(${rawString(protoRec.name)})`; + rhs = this._observe(`${localsAccessor}.get(${rawString(protoRec.name)})`, protoRec); break; case RecordType.INVOKE_METHOD: - rhs = `${context}.${protoRec.name}(${argString})`; + rhs = this._observe(`${context}.${protoRec.name}(${argString})`, protoRec); break; case RecordType.SAFE_INVOKE_METHOD: + var invoke = `${context}.${protoRec.name}(${argString})`; rhs = - `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}(${argString})`; + `${this._utilName}.isValueBlank(${context}) ? null : ${this._observe(invoke, protoRec)}`; break; case RecordType.INVOKE_CLOSURE: @@ -87,7 +96,7 @@ export class CodegenLogicUtil { break; case RecordType.KEYED_READ: - rhs = `${context}[${getLocalName(protoRec.args[0])}]`; + rhs = this._observe(`${context}[${getLocalName(protoRec.args[0])}]`, protoRec); break; case RecordType.KEYED_WRITE: @@ -104,6 +113,14 @@ export class CodegenLogicUtil { return `${getLocalName(protoRec.selfIndex)} = ${rhs};`; } + _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})`; + } else { + return exp; + } + } _genInterpolation(protoRec: ProtoRecord): string { var iVals = []; diff --git a/modules/angular2/src/change_detection/codegen_name_util.ts b/modules/angular2/src/change_detection/codegen_name_util.ts index c3f9bc4a8d..8e99ec5d3f 100644 --- a/modules/angular2/src/change_detection/codegen_name_util.ts +++ b/modules/angular2/src/change_detection/codegen_name_util.ts @@ -124,7 +124,7 @@ export class CodegenNameUtil { MapWrapper.forEach(this._sanitizedEventNames, (names, eb) => { for (var i = 0; i < names.length; ++i) { if (i !== CONTEXT_INDEX) { - res.push(this.getEventLocalName(eb, i)); + res.push(`${this.getEventLocalName(eb, i)}`); } } }); @@ -155,7 +155,7 @@ export class CodegenNameUtil { for (var j = 0, jLen = this.directiveRecords.length; j < jLen; ++j) { var dRec = this.directiveRecords[j]; fieldList.push(this.getDirectiveName(dRec.directiveIndex)); - if (dRec.isOnPushChangeDetection()) { + if (!dRec.isDefaultChangeDetection()) { fieldList.push(this.getDetectorName(dRec.directiveIndex)); } } @@ -202,7 +202,7 @@ export class CodegenNameUtil { getAllDetectorNames(): List { return ListWrapper.map( - ListWrapper.filter(this.directiveRecords, r => r.isOnPushChangeDetection()), + ListWrapper.filter(this.directiveRecords, r => !r.isDefaultChangeDetection()), (d) => this.getDetectorName(d.directiveIndex)); } diff --git a/modules/angular2/src/change_detection/constants.ts b/modules/angular2/src/change_detection/constants.ts index 198905647b..c481ebc8eb 100644 --- a/modules/angular2/src/change_detection/constants.ts +++ b/modules/angular2/src/change_detection/constants.ts @@ -1,4 +1,5 @@ // TODO:vsavkin Use enums after switching to TypeScript +import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang'; /** * CHECK_ONCE means that after calling detectChanges the mode of the change detector @@ -33,3 +34,7 @@ export const ON_PUSH: string = "ON_PUSH"; * DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration. */ export const DEFAULT: string = "DEFAULT"; + +export function isDefaultChangeDetectionStrategy(changeDetectionStrategy: string): boolean { + return isBlank(changeDetectionStrategy) || StringWrapper.equals(changeDetectionStrategy, DEFAULT); +} \ No newline at end of file diff --git a/modules/angular2/src/change_detection/directive_record.ts b/modules/angular2/src/change_detection/directive_record.ts index edfadd2668..3658414b72 100644 --- a/modules/angular2/src/change_detection/directive_record.ts +++ b/modules/angular2/src/change_detection/directive_record.ts @@ -1,5 +1,5 @@ -import {ON_PUSH} from './constants'; -import {StringWrapper, normalizeBool} from 'angular2/src/facade/lang'; +import {StringWrapper, normalizeBool, isBlank} from 'angular2/src/facade/lang'; +import {isDefaultChangeDetectionStrategy} from './constants'; export class DirectiveIndex { constructor(public elementIndex: number, public directiveIndex: number) {} @@ -32,5 +32,7 @@ export class DirectiveRecord { this.changeDetection = changeDetection; } - isOnPushChangeDetection(): boolean { return StringWrapper.equals(this.changeDetection, ON_PUSH); } + isDefaultChangeDetection(): boolean { + return isDefaultChangeDetectionStrategy(this.changeDetection); + } } \ 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 4240e26e47..ef6d98d557 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.ts +++ b/modules/angular2/src/change_detection/dynamic_change_detector.ts @@ -1,4 +1,10 @@ -import {isPresent, isBlank, BaseException, FunctionWrapper} from 'angular2/src/facade/lang'; +import { + isPresent, + isBlank, + BaseException, + FunctionWrapper, + StringWrapper +} from 'angular2/src/facade/lang'; import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection'; import {AbstractChangeDetector} from './abstract_change_detector'; @@ -64,7 +70,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } _markPathAsCheckOnce(proto: ProtoRecord): void { - if (proto.bindingRecord.isOnPushChangeDetection()) { + if (!proto.bindingRecord.isDefaultChangeDetection()) { var dir = proto.bindingRecord.directiveRecord; this._getDetectorFor(dir.directiveIndex).markPathToRootAsCheckOnce(); } @@ -136,7 +142,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (proto.lastInDirective) { changes = null; - if (isChanged && bindingRecord.isOnPushChangeDetection()) { + if (isChanged && !bindingRecord.isDefaultChangeDetection()) { this._getDetectorFor(directiveRecord.directiveIndex).markAsCheckOnce(); } @@ -198,7 +204,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { return null; } - var currValue = this._calculateCurrValue(proto, values, locals); + var currValue = this.observe(this._calculateCurrValue(proto, values, locals), proto.selfIndex); if (proto.shouldBeChecked()) { var prevValue = this._readSelf(proto, values); if (!isSame(prevValue, currValue)) { diff --git a/modules/angular2/src/change_detection/observable_facade.dart b/modules/angular2/src/change_detection/observable_facade.dart new file mode 100644 index 0000000000..4f48465ceb --- /dev/null +++ b/modules/angular2/src/change_detection/observable_facade.dart @@ -0,0 +1,3 @@ +import 'package:observe/observe.dart'; + +bool isObservable(value) => value is Observable; \ No newline at end of file diff --git a/modules/angular2/src/change_detection/observable_facade.ts b/modules/angular2/src/change_detection/observable_facade.ts new file mode 100644 index 0000000000..ed632cd5a0 --- /dev/null +++ b/modules/angular2/src/change_detection/observable_facade.ts @@ -0,0 +1,3 @@ +export function isObservable(value: any): boolean { + return false; +} \ No newline at end of file diff --git a/modules/angular2/src/directives/observable_list_diff.dart b/modules/angular2/src/directives/observable_list_diff.dart index 1bf446575c..c9bdc1b812 100644 --- a/modules/angular2/src/directives/observable_list_diff.dart +++ b/modules/angular2/src/directives/observable_list_diff.dart @@ -48,10 +48,8 @@ class ObservableListDiff extends DefaultIterableDiffer { return super.diff(collection); // No updates has been registered. - // Returning this tells change detection that object has not change, - // so it should NOT update the binding. } else { - return this; + return null; } } } 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 fdb3a5b485..d1a2b1ba6b 100644 --- a/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart +++ b/modules/angular2/src/transform/template_compiler/change_detector_codegen.dart @@ -100,7 +100,7 @@ class _CodegenState { var protoRecords = createPropertyRecords(def); var eventBindings = createEventRecords(def); var names = new CodegenNameUtil(protoRecords, eventBindings, def.directiveRecords, _UTIL); - var logic = new CodegenLogicUtil(names, _UTIL); + var logic = new CodegenLogicUtil(names, _UTIL, def.strategy); return new _CodegenState._( def.id, typeName, @@ -193,7 +193,7 @@ class _CodegenState { String _genMarkPathToRootAsCheckOnce(ProtoRecord r) { var br = r.bindingRecord; - if (br.isOnPushChangeDetection()) { + if (!br.isDefaultChangeDetection()) { return "${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markPathToRootAsCheckOnce();"; } else { return ""; @@ -469,7 +469,7 @@ class _CodegenState { String _genNotifyOnPushDetectors(ProtoRecord r) { var br = r.bindingRecord; - if (!r.lastInDirective || !br.isOnPushChangeDetection()) return ''; + if (!r.lastInDirective || br.isDefaultChangeDetection()) return ''; return ''' if($_IS_CHANGED_LOCAL) { ${_names.getDetectorName(br.directiveRecord.directiveIndex)}.markAsCheckOnce(); diff --git a/modules/angular2/test/change_detection/change_detector_config.ts b/modules/angular2/test/change_detection/change_detector_config.ts index c15149fcd7..baf8d81a1c 100644 --- a/modules/angular2/test/change_detection/change_detector_config.ts +++ b/modules/angular2/test/change_detection/change_detector_config.ts @@ -103,7 +103,14 @@ export function getDefinition(id: string): TestDefinition { let cdDef = new ChangeDetectorDefinition(id, null, [], [], eventRecords, [_DirectiveUpdating.basicRecords[0]], true); testDef = new TestDefinition(id, cdDef, null); + + } else if (id == "onPushObserve") { + var records = _createBindingRecords("a"); + let cdDef = new ChangeDetectorDefinition(id, "ON_PUSH_OBSERVE", [], records, [], [], false); + testDef = new TestDefinition(id, cdDef, null); } + + if (isBlank(testDef)) { throw `No ChangeDetectorDefinition for ${id} available. Please modify this file if necessary.`; } @@ -129,6 +136,7 @@ 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"]); 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 288319aebf..34a8ff264c 100644 --- a/modules/angular2/test/change_detection/change_detector_spec.ts +++ b/modules/angular2/test/change_detection/change_detector_spec.ts @@ -1,5 +1,16 @@ /// -import {ddescribe, describe, it, iit, xit, expect, beforeEach, afterEach} from 'angular2/test_lib'; +import { + ddescribe, + describe, + it, + iit, + xit, + expect, + beforeEach, + afterEach, + tick, + fakeAsync +} from 'angular2/test_lib'; import { CONST_EXPR, @@ -40,6 +51,7 @@ import {Pipes} from 'angular2/src/change_detection/pipes'; import {JitProtoChangeDetector} from 'angular2/src/change_detection/jit_proto_change_detector'; import {getDefinition} from './change_detector_config'; +import {createObservableModel} from './change_detector_spec_util'; import {getFactoryById} from './generated/change_detector_classes'; import {IS_DART} from '../platform'; @@ -735,6 +747,67 @@ export function main() { expect(checkedDetector.mode).toEqual(CHECK_ONCE); }); + + 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(); + + var cd = _createWithoutHydrate('onPushObserve').changeDetector; + cd.hydrate(context, null, directives, null); + cd.detectChanges(); + + expect(cd.mode).toEqual(CHECKED); + + context.a.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('onPushObserve').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('onPushObserve').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/change_detection/change_detector_spec_util.dart b/modules/angular2/test/change_detection/change_detector_spec_util.dart new file mode 100644 index 0000000000..889e71d6cb --- /dev/null +++ b/modules/angular2/test/change_detection/change_detector_spec_util.dart @@ -0,0 +1,27 @@ +library angular.change_detection.change_detector_spec_util; + +import 'package:observe/observe.dart' show Observable; +import 'dart:async'; + +dynamic createObservableModel() { + return new Entity(); +} + +class Entity implements Observable { + Stream changes; + StreamController controller; + + Entity() { + controller = new StreamController.broadcast(); + changes = controller.stream; + } + + pushUpdate() { + controller.add("new"); + } + + bool get hasObservers => null; + bool deliverChanges() => null; + notifyPropertyChange(Symbol field, Object oldValue, Object newValue) => null; + void notifyChange(record) {} +} \ No newline at end of file diff --git a/modules/angular2/test/change_detection/change_detector_spec_util.ts b/modules/angular2/test/change_detection/change_detector_spec_util.ts new file mode 100644 index 0000000000..7710fd2e94 --- /dev/null +++ b/modules/angular2/test/change_detection/change_detector_spec_util.ts @@ -0,0 +1,3 @@ +export function createObservableModel(): any { + return null; +} \ No newline at end of file diff --git a/modules/angular2/test/directives/observable_list_diff_spec.dart b/modules/angular2/test/directives/observable_list_diff_spec.dart index 8187dea3c1..745592c578 100644 --- a/modules/angular2/test/directives/observable_list_diff_spec.dart +++ b/modules/angular2/test/directives/observable_list_diff_spec.dart @@ -36,7 +36,7 @@ main() { d.diff(c); - expect(d.diff(c)).toBe(d); + expect(d.diff(c)).toBe(null); }); it("should return the wrapped value once a change has been triggered", @@ -50,7 +50,7 @@ main() { c.add(3); // same value, because we have not detected the change yet - expect(d.diff(c)).toBe(d); + expect(d.diff(c)).toBe(null); // now we detect the change flushMicrotasks(); @@ -92,7 +92,7 @@ main() { // pushing into the first collection has no effect, and we do not see the change c1.add(3); - expect(d.diff(c2)).toBe(d); + expect(d.diff(c2)).toBe(null); }); }); }