feat(change_detection): updated handling ON_PUSH detectors so they get notified when their bindings change

This commit is contained in:
vsavkin
2015-04-14 08:54:09 -07:00
parent dc9c614da2
commit 68faddbf5c
13 changed files with 249 additions and 60 deletions

View File

@ -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) {

View File

@ -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;
}

View File

@ -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(", ");
}

View File

@ -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() {

View File

@ -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;