diff --git a/modules/angular2/src/change_detection/abstract_change_detector.js b/modules/angular2/src/change_detection/abstract_change_detector.js index 97f854b236..896f623b7b 100644 --- a/modules/angular2/src/change_detection/abstract_change_detector.js +++ b/modules/angular2/src/change_detection/abstract_change_detector.js @@ -9,13 +9,13 @@ export class AbstractChangeDetector extends ChangeDetector { shadowDomChildren:List; parent:ChangeDetector; mode:string; - changeDetectorRef:ChangeDetectorRef; + ref:ChangeDetectorRef; constructor() { super(); this.lightDomChildren = []; this.shadowDomChildren = []; - this.changeDetectorRef = new ChangeDetectorRef(this); + this.ref = new ChangeDetectorRef(this); this.mode = null; } @@ -80,6 +80,10 @@ export class AbstractChangeDetector extends ChangeDetector { } } + markAsCheckOnce() { + this.mode = CHECK_ONCE; + } + markPathToRootAsCheckOnce() { var c = this; while(isPresent(c) && c.mode != DETACHED) { diff --git a/modules/angular2/src/change_detection/binding_record.js b/modules/angular2/src/change_detection/binding_record.js index 7ec5422d51..ddcb8c67dd 100644 --- a/modules/angular2/src/change_detection/binding_record.js +++ b/modules/angular2/src/change_detection/binding_record.js @@ -32,6 +32,10 @@ export class BindingRecord { return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange; } + isOnPushChangeDetection() { + return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection(); + } + isDirective() { return this.mode === DIRECTIVE; } diff --git a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 index c8a774e4c8..d53b0b43ee 100644 --- a/modules/angular2/src/change_detection/change_detection_jit_generator.es6 +++ b/modules/angular2/src/change_detection/change_detection_jit_generator.es6 @@ -36,7 +36,7 @@ var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry"; var PROTOS_ACCESSOR = "this.protos"; var DIRECTIVES_ACCESSOR = "this.directiveRecords"; var CONTEXT_ACCESSOR = "this.context"; -var CHANGE_LOCAL = "change"; +var IS_CHANGED_LOCAL = "isChanged"; var CHANGES_LOCAL = "changes"; var LOCALS_ACCESSOR = "this.locals"; var MODE_ACCESSOR = "this.mode"; @@ -78,10 +78,15 @@ function pipeOnDestroyTemplate(pipeNames:List) { } function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipeOnDestroy:string, - directiveFieldNames:List):string { + directiveFieldNames:List, detectorFieldNames:List):string { var directiveInit = ""; for(var i = 0; i < directiveFieldNames.length; ++i) { - directiveInit += `${directiveFieldNames[i]} = directives.directive(this.directiveRecords[${i}]);\n`; + directiveInit += `${directiveFieldNames[i]} = directives.getDirectiveFor(this.directiveRecords[${i}]);\n`; + } + + var detectorInit = ""; + for(var i = 0; i < detectorFieldNames.length; ++i) { + detectorInit += `${detectorFieldNames[i]} = directives.getDetectorFor(this.directiveRecords[${i}]);\n`; } return ` @@ -90,6 +95,7 @@ ${type}.prototype.hydrate = function(context, locals, directives) { ${CONTEXT_ACCESSOR} = context; ${LOCALS_ACCESSOR} = locals; ${directiveInit} + ${detectorInit} } ${type}.prototype.dehydrate = function() { ${pipeOnDestroy} @@ -128,7 +134,7 @@ function detectChangesBodyTemplate(localDefinitions:string, changeDefinitions:st ${localDefinitions} ${changeDefinitions} var ${TEMP_LOCAL}; -var ${CHANGE_LOCAL}; +var ${IS_CHANGED_LOCAL} = false; var ${CURRENT_PROTO}; var ${CHANGES_LOCAL} = null; @@ -208,6 +214,7 @@ function updateDirectiveTemplate(oldValue:string, newValue:string, directiveProp return ` if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue})); ${directiveProperty} = ${newValue}; +${IS_CHANGED_LOCAL} = true; `; } @@ -227,6 +234,22 @@ if(${CHANGES_LOCAL}) { `; } +function notifyOnPushDetectorsTemplate(detector:string):string{ + return ` +if(${IS_CHANGED_LOCAL}) { + ${detector}.markAsCheckOnce(); +} +`; +} + +function lastInDirectiveTemplate(notifyOnChanges:string, notifyOnPush:string):string{ + return ` +${notifyOnChanges} +${notifyOnPush} +${IS_CHANGED_LOCAL} = false; +`; +} + export class ChangeDetectorJITGenerator { typeName:string; @@ -285,22 +308,32 @@ export class ChangeDetectorJITGenerator { genHydrate():string { var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy); return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(), - pipeOnDestroyTemplate(this.getNonNullPipeNames()), this.getDirectiveFieldNames()); + pipeOnDestroyTemplate(this.getNonNullPipeNames()), + this.getDirectiveFieldNames(), this.getDetectorFieldNames()); } getDirectiveFieldNames():List { return this.directiveRecords.map((d) => this.getDirective(d)); } + getDetectorFieldNames():List { + return this.directiveRecords.filter(r => r.isOnPushChangeDetection()).map((d) => this.getDetector(d)); + } + getDirective(d:DirectiveRecord) { return `this.directive_${d.name}`; } + getDetector(d:DirectiveRecord) { + return `this.detector_${d.name}`; + } + genFieldDefinitions() { var fields = []; fields = fields.concat(this.fieldNames); fields = fields.concat(this.getNonNullPipeNames()); fields = fields.concat(this.getDirectiveFieldNames()); + fields = fields.concat(this.getDetectorFieldNames()); return fieldDefinitionsTemplate(fields); } @@ -362,11 +395,11 @@ export class ChangeDetectorJITGenerator { var change = this.changeNames[r.selfIndex]; var pipe = this.pipeNames[r.selfIndex]; - var cdRef = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.changeDetectorRef" : "null"; + var cdRef = r.mode === RECORD_TYPE_BINDING_PIPE ? "this.ref" : "null"; var update = this.genUpdateDirectiveOrElement(r); var addToChanges = this.genAddToChanges(r); - var lastInDirective = this.genNotifyOnChanges(r); + var lastInDirective = this.genLastInDirective(r); return pipeCheckTemplate(r.selfIndex - 1, context, cdRef, pipe, r.name, oldValue, newValue, change, update, addToChanges, lastInDirective); @@ -380,7 +413,7 @@ export class ChangeDetectorJITGenerator { var update = this.genUpdateDirectiveOrElement(r); var addToChanges = this.genAddToChanges(r); - var lastInDirective = this.genNotifyOnChanges(r); + var lastInDirective = this.genLastInDirective(r); var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change, update, addToChanges, lastInDirective); @@ -471,6 +504,12 @@ export class ChangeDetectorJITGenerator { return r.bindingRecord.callOnChange() ? addToChangesTemplate(oldValue, newValue) : ""; } + genLastInDirective(r:ProtoRecord):string{ + var onChanges = this.genNotifyOnChanges(r); + var onPush = this.genNotifyOnPushDetectors(r); + return lastInDirectiveTemplate(onChanges, onPush); + } + genNotifyOnChanges(r:ProtoRecord):string{ var br = r.bindingRecord; if (r.lastInDirective && br.callOnChange()) { @@ -480,6 +519,15 @@ export class ChangeDetectorJITGenerator { } } + genNotifyOnPushDetectors(r:ProtoRecord):string{ + var br = r.bindingRecord; + if (r.lastInDirective && br.isOnPushChangeDetection()) { + return notifyOnPushDetectorsTemplate(this.getDetector(br.directiveRecord)); + } else { + return ""; + } + } + genArgs(r:ProtoRecord):string { return r.args.map((arg) => this.localNames[arg]).join(", "); } diff --git a/modules/angular2/src/change_detection/directive_record.js b/modules/angular2/src/change_detection/directive_record.js index c9715f0abc..ef6a6fc714 100644 --- a/modules/angular2/src/change_detection/directive_record.js +++ b/modules/angular2/src/change_detection/directive_record.js @@ -1,16 +1,24 @@ +import {ON_PUSH} from './constants'; +import {StringWrapper} from 'angular2/src/facade/lang'; + export class DirectiveRecord { elementIndex:number; directiveIndex:number; callOnAllChangesDone:boolean; callOnChange:boolean; + changeDetection:string; constructor(elementIndex:number, directiveIndex:number, - callOnAllChangesDone:boolean, - callOnChange:boolean) { + callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) { this.elementIndex = elementIndex; this.directiveIndex = directiveIndex; this.callOnAllChangesDone = callOnAllChangesDone; this.callOnChange = callOnChange; + this.changeDetection = changeDetection; + } + + isOnPushChangeDetection():boolean { + return StringWrapper.equals(this.changeDetection, ON_PUSH); } get name() { diff --git a/modules/angular2/src/change_detection/dynamic_change_detector.js b/modules/angular2/src/change_detection/dynamic_change_detector.js index 3d5504d189..a4cfae3263 100644 --- a/modules/angular2/src/change_detection/dynamic_change_detector.js +++ b/modules/angular2/src/change_detection/dynamic_change_detector.js @@ -95,19 +95,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector { var protos:List = this.protos; var changes = null; + var isChanged = false; for (var i = 0; i < protos.length; ++i) { var proto:ProtoRecord = protos[i]; + var bindingRecord = proto.bindingRecord; + var directiveRecord = bindingRecord.directiveRecord; var change = this._check(proto); if (isPresent(change)) { if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change); - this._updateDirectiveOrElement(change, proto.bindingRecord); - changes = this._addChange(proto.bindingRecord, change, changes); + this._updateDirectiveOrElement(change, bindingRecord); + isChanged = true; + changes = this._addChange(bindingRecord, change, changes); } - if (proto.lastInDirective && isPresent(changes)) { - this._directive(proto.bindingRecord.directiveRecord).onChange(changes); - changes = null; + if (proto.lastInDirective) { + if (isPresent(changes)) { + this._getDirectiveFor(directiveRecord).onChange(changes); + changes = null; + } + + if (isChanged && bindingRecord.isOnPushChangeDetection()) { + this._getDetectorFor(directiveRecord).markAsCheckOnce(); + } + + isChanged = false; } } } @@ -117,7 +129,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { for (var i = dirs.length - 1; i >= 0; --i) { var dir = dirs[i]; if (dir.callOnAllChangesDone) { - this._directive(dir).onAllChangesDone(); + this._getDirectiveFor(dir).onAllChangesDone(); } } } @@ -126,7 +138,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { if (isBlank(bindingRecord.directiveRecord)) { this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue); } else { - bindingRecord.setter(this._directive(bindingRecord.directiveRecord), change.currentValue); + bindingRecord.setter(this._getDirectiveFor(bindingRecord.directiveRecord), change.currentValue); } } @@ -138,8 +150,12 @@ export class DynamicChangeDetector extends AbstractChangeDetector { } } - _directive(directive:DirectiveRecord) { - return this.directives.directive(directive); + _getDirectiveFor(directive:DirectiveRecord) { + return this.directives.getDirectiveFor(directive); + } + + _getDetectorFor(directive:DirectiveRecord) { + return this.directives.getDetectorFor(directive); } _check(proto:ProtoRecord) { @@ -249,7 +265,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector { // // In the future, pipes declared in the bind configuration should // be able to access the changeDetectorRef of that component. - var cdr = proto.mode === RECORD_TYPE_BINDING_PIPE ? this.changeDetectorRef : null; + var cdr = proto.mode === RECORD_TYPE_BINDING_PIPE ? this.ref : null; var pipe = this.pipeRegistry.get(proto.name, context, cdr); this._writePipe(proto, pipe); return pipe; diff --git a/modules/angular2/src/core/annotations/annotations.js b/modules/angular2/src/core/annotations/annotations.js index 50ca758372..a803f1bc8a 100644 --- a/modules/angular2/src/core/annotations/annotations.js +++ b/modules/angular2/src/core/annotations/annotations.js @@ -1,6 +1,7 @@ import {ABSTRACT, CONST, normalizeBlank, isPresent} from 'angular2/src/facade/lang'; import {ListWrapper, List} from 'angular2/src/facade/collection'; import {Injectable} from 'angular2/di'; +import {DEFAULT} from 'angular2/change_detection'; // type StringMap = {[idx: string]: string}; @@ -553,7 +554,7 @@ export class Component extends Directive { hostListeners, injectables, lifecycle, - changeDetection + changeDetection = DEFAULT }:{ selector:string, properties:Object, diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index 50fd68b65a..3cd5b01afb 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -8,7 +8,7 @@ import * as viewModule from 'angular2/src/core/compiler/view'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations'; -import {ChangeDetectorRef} from 'angular2/change_detection'; +import {ChangeDetector, ChangeDetectorRef} from 'angular2/change_detection'; import {QueryList} from './query_list'; var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10; @@ -277,6 +277,15 @@ export class DirectiveBinding extends ResolvedBinding { } } + get changeDetection() { + if (this.annotation instanceof Component) { + var c:Component = this.annotation; + return c.changeDetection; + } else { + return null; + } + } + static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding { var rb = b.resolve(); var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom); @@ -298,13 +307,13 @@ export class PreBuiltObjects { view:viewModule.AppView; element:NgElement; viewContainer:ViewContainer; - changeDetectorRef:ChangeDetectorRef; + changeDetector:ChangeDetector; constructor(view, element:NgElement, viewContainer:ViewContainer, - changeDetectorRef:ChangeDetectorRef) { + changeDetector:ChangeDetector) { this.view = view; this.element = element; this.viewContainer = viewContainer; - this.changeDetectorRef = changeDetectorRef; + this.changeDetector = changeDetector; } } @@ -603,6 +612,10 @@ export class ElementInjector extends TreeNode { return this._preBuiltObjects.element; } + getChangeDetector() { + return this._preBuiltObjects.changeDetector; + } + getComponent() { if (this._proto._binding0IsComponent) { return this._obj0; @@ -885,7 +898,7 @@ export class ElementInjector extends TreeNode { if (keyId === staticKeys.viewId) return this._preBuiltObjects.view; if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element; if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer; - if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetectorRef; + if (keyId === staticKeys.changeDetectorRefId) return this._preBuiltObjects.changeDetector.ref; //TODO add other objects as needed return _undefined; diff --git a/modules/angular2/src/core/compiler/view.js b/modules/angular2/src/core/compiler/view.js index 6e68c644cb..eab9b92f30 100644 --- a/modules/angular2/src/core/compiler/view.js +++ b/modules/angular2/src/core/compiler/view.js @@ -256,11 +256,16 @@ export class AppView { } } - directive(directive:DirectiveRecord) { - var elementInjector:ElementInjector = this.elementInjectors[directive.elementIndex]; + getDirectiveFor(directive:DirectiveRecord) { + var elementInjector = this.elementInjectors[directive.elementIndex]; return elementInjector.getDirectiveAtIndex(directive.directiveIndex); } + getDetectorFor(directive:DirectiveRecord) { + var elementInjector = this.elementInjectors[directive.elementIndex]; + return elementInjector.getChangeDetector(); + } + setDynamicComponentChildView(boundElementIndex, view:AppView) { if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) { throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`); @@ -458,9 +463,11 @@ export class AppProtoView { if (!MapWrapper.contains(this._directiveRecordsMap, id)) { var binding = protoElementInjector.getDirectiveBindingAtIndex(directiveIndex); + var changeDetection = binding.changeDetection; + MapWrapper.set(this._directiveRecordsMap, id, new DirectiveRecord(elementInjectorIndex, directiveIndex, - binding.callOnAllChangesDone, binding.callOnChange)); + binding.callOnAllChangesDone, binding.callOnChange, changeDetection)); } return MapWrapper.get(this._directiveRecordsMap, id); diff --git a/modules/angular2/src/core/compiler/view_factory.js b/modules/angular2/src/core/compiler/view_factory.js index 036bbd35b3..3c0b16028a 100644 --- a/modules/angular2/src/core/compiler/view_factory.js +++ b/modules/angular2/src/core/compiler/view_factory.js @@ -5,7 +5,6 @@ import {isPresent, isBlank, BaseException} from 'angular2/src/facade/lang'; import {NgElement} from 'angular2/src/core/compiler/ng_element'; import * as vcModule from './view_container'; import * as viewModule from './view'; -import {ChangeDetectorRef} from 'angular2/change_detection'; // TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this! export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity'; @@ -77,13 +76,12 @@ export class ViewFactory { elementInjectors[binderIdx] = elementInjector; // componentChildViews - var changeDetectorRef = null; + var childChangeDetector = null; if (binder.hasStaticComponent()) { var childView = this._createView(binder.nestedProtoView); - changeDetector.addShadowDomChild(childView.changeDetector); - - changeDetectorRef = new ChangeDetectorRef(childView.changeDetector); - + childChangeDetector = childView.changeDetector; + changeDetector.addShadowDomChild(childChangeDetector); + componentChildViews[binderIdx] = childView; } @@ -97,7 +95,7 @@ export class ViewFactory { // preBuiltObjects if (isPresent(elementInjector)) { preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer, - changeDetectorRef); + childChangeDetector); } } diff --git a/modules/angular2/test/change_detection/change_detection_spec.js b/modules/angular2/test/change_detection/change_detection_spec.js index 93f6d43b00..5e8d78dddf 100644 --- a/modules/angular2/test/change_detection/change_detection_spec.js +++ b/modules/angular2/test/change_detection/change_detection_spec.js @@ -28,7 +28,7 @@ export function main() { } function dirs(directives:List) { - return new FakeDirectives(directives); + return new FakeDirectives(directives, []); } function convertLocalsToVariableBindings(locals) { @@ -246,9 +246,9 @@ export function main() { }); describe("updating directives", () => { - var dirRecord1 = new DirectiveRecord(0, 0, true, true); - var dirRecord2 = new DirectiveRecord(0, 1, true, true); - var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false); + var dirRecord1 = new DirectiveRecord(0, 0, true, true, DEFAULT); + var dirRecord2 = new DirectiveRecord(0, 1, true, true, DEFAULT); + var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false, DEFAULT); function updateA(exp:string, dirRecord) { return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, dirRecord); @@ -553,6 +553,44 @@ export function main() { expect(cd.mode).toEqual(CHECK_ALWAYS); }); + + describe("marking ON_PUSH detectors as CHECK_ONCE after an update", () => { + var checkedDetector; + var dirRecordWithOnPush; + var updateDirWithOnPushRecord; + var directives; + + beforeEach(() => { + var proto = createProtoChangeDetector(null, ON_PUSH); + checkedDetector = instantiate(proto, null, [], []); + checkedDetector.hydrate(null, null, null); + checkedDetector.mode = CHECKED; + + // this directive is a component with ON_PUSH change detection + dirRecordWithOnPush = new DirectiveRecord(0, 0, false, false, ON_PUSH); + + // a record updating a component + updateDirWithOnPushRecord = + BindingRecord.createForDirective(ast("42"), "a", (o,v) => o.a = v, dirRecordWithOnPush); + + var targetDirective = new TestData(null); + directives = new FakeDirectives([targetDirective], [checkedDetector]); + }); + + it("should set the mode to CHECK_ONCE when a binding is updated", () => { + var proto = createProtoChangeDetector(null); + + var cd = instantiate(proto, null, [updateDirWithOnPushRecord], [dirRecordWithOnPush]); + cd.hydrate(null, null, directives); + + expect(checkedDetector.mode).toEqual(CHECKED); + + // evaluate the record, update the targetDirective, and mark its detector as CHECK_ONCE + cd.detectChanges(); + + expect(checkedDetector.mode).toEqual(CHECK_ONCE); + }); + }); }); describe("markPathToRootAsCheckOnce", () => { @@ -664,7 +702,7 @@ export function main() { expect(pipe.destroyCalled).toEqual(true); }); - it("should inject the binding propagation configuration " + + it("should inject the ChangeDetectorRef " + "of the encompassing component into a pipe", () => { var registry = new FakePipeRegistry('pipe', () => new IdentityPipe()); @@ -673,7 +711,7 @@ export function main() { cd.detectChanges(); - expect(registry.cdRef).toBe(cd.changeDetectorRef); + expect(registry.cdRef).toBe(cd.ref); }); }); @@ -853,14 +891,20 @@ class TestData { class FakeDirectives { directives:List; + detectors:List; - constructor(directives:List) { + constructor(directives:List, detectors:List) { this.directives = directives; + this.detectors = detectors; } - directive(directiveRecord:DirectiveRecord) { + getDirectiveFor(directiveRecord:DirectiveRecord) { return this.directives[directiveRecord.directiveIndex]; } + + getDetectorFor(directiveRecord:DirectiveRecord) { + return this.detectors[directiveRecord.directiveIndex]; + } } class TestDispatcher extends ChangeDispatcher { diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index c52ab8a0a8..a2efbcd96f 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -11,7 +11,7 @@ import {AppProtoView, AppView} from 'angular2/src/core/compiler/view'; import {ViewContainer} from 'angular2/src/core/compiler/view_container'; import {NgElement} from 'angular2/src/core/compiler/ng_element'; import {Directive} from 'angular2/src/core/annotations/annotations'; -import {ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection'; +import {DynamicChangeDetector, ChangeDetectorRef, Parser, Lexer} from 'angular2/change_detection'; import {ViewRef, Renderer, EventBinding} from 'angular2/src/render/api'; import {QueryList} from 'angular2/src/core/compiler/query_list'; @@ -621,10 +621,10 @@ export function main() { }); it('should return changeDetectorRef', function () { - var config = new ChangeDetectorRef(null); - var inj = injector([], null, null, new PreBuiltObjects(null, null, null, config)); + var cd = new DynamicChangeDetector(null, null, null, [], []); + var inj = injector([], null, null, new PreBuiltObjects(null, null, null, cd)); - expect(inj.get(ChangeDetectorRef)).toEqual(config); + expect(inj.get(ChangeDetectorRef)).toBe(cd.ref); }); }); diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 02898c2d23..83e5c88d02 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -390,13 +390,13 @@ export function main() { }) })); - describe("ChangeDetectorRef", () => { - it("can be used to disable the change detection of the component's template", + describe("ON_PUSH components", () => { + it("should use ChangeDetectorRef to manually request a check", inject([TestBed, AsyncTestCompleter], (tb, async) => { tb.overrideView(MyComp, new View({ - template: '', - directives: [[[PushBasedComp]]] + template: '', + directives: [[[PushCmpWithRef]]] })); tb.createView(MyComp, {context: ctx}).then((view) => { @@ -417,10 +417,33 @@ export function main() { }) })); - it('should not affect updating properties on the component', inject([TestBed, AsyncTestCompleter], (tb, async) => { + it("should be checked when its bindings got updated", + inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ template: '', - directives: [[[PushBasedComp]]] + directives: [[[PushCmp]]] + })); + + tb.createView(MyComp, {context: ctx}).then((view) => { + var cmp = view.rawView.locals.get('cmp'); + + ctx.ctxProp = "one"; + view.detectChanges(); + expect(cmp.numberOfChecks).toEqual(1); + + ctx.ctxProp = "two"; + view.detectChanges(); + expect(cmp.numberOfChecks).toEqual(2); + + async.done(); + }) + })); + + it('should not affect updating properties on the component', inject([TestBed, AsyncTestCompleter], (tb, async) => { + tb.overrideView(MyComp, new View({ + template: '', + directives: [[[PushCmpWithRef]]] })); tb.createView(MyComp, {context: ctx}).then((view) => { @@ -800,7 +823,29 @@ class MyDir { changeDetection:ON_PUSH }) @View({template: '{{field}}'}) -class PushBasedComp { +class PushCmp { + numberOfChecks:number; + prop; + + constructor() { + this.numberOfChecks = 0; + } + + get field(){ + this.numberOfChecks++; + return "fixed"; + } +} + + @Component({ + selector: 'push-cmp-with-ref', + properties: { + 'prop': 'prop' + }, + changeDetection:ON_PUSH +}) +@View({template: '{{field}}'}) +class PushCmpWithRef { numberOfChecks:number; ref:ChangeDetectorRef; prop; diff --git a/modules/benchmarks/src/change_detection/change_detection_benchmark.js b/modules/benchmarks/src/change_detection/change_detection_benchmark.js index b4e4c84769..84777fd173 100644 --- a/modules/benchmarks/src/change_detection/change_detection_benchmark.js +++ b/modules/benchmarks/src/change_detection/change_detection_benchmark.js @@ -12,7 +12,8 @@ import { dynamicChangeDetection, jitChangeDetection, BindingRecord, - DirectiveRecord + DirectiveRecord, + DEFAULT } from 'angular2/change_detection'; @@ -191,7 +192,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec var proto = changeDetection.createProtoChangeDetector("proto"); - var directiveRecord = new DirectiveRecord(0, 0, false, false); + var directiveRecord = new DirectiveRecord(0, 0, false, false, DEFAULT); var bindings = [ BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord), BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord), @@ -306,7 +307,7 @@ class FakeDirectives { this.targetObj = targetObj; } - directive(record) { + getDirectiveFor(record) { return this.targetObj; } }