feat(change_detection): updated handling ON_PUSH detectors so they get notified when their bindings change
This commit is contained in:
parent
dc9c614da2
commit
68faddbf5c
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>):string {
|
||||
directiveFieldNames:List<String>, detectorFieldNames:List<String>):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<string> {
|
||||
return this.directiveRecords.map((d) => this.getDirective(d));
|
||||
}
|
||||
|
||||
getDetectorFieldNames():List<string> {
|
||||
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(", ");
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -95,19 +95,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
var protos:List<ProtoRecord> = 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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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: '<push-cmp #cmp></push-cmp>',
|
||||
directives: [[[PushBasedComp]]]
|
||||
template: '<push-cmp-with-ref #cmp></push-cmp-with-ref>',
|
||||
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: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
|
||||
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: '<push-cmp-with-ref [prop]="ctxProp" #cmp></push-cmp-with-ref>',
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue