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;
|
shadowDomChildren:List;
|
||||||
parent:ChangeDetector;
|
parent:ChangeDetector;
|
||||||
mode:string;
|
mode:string;
|
||||||
changeDetectorRef:ChangeDetectorRef;
|
ref:ChangeDetectorRef;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.lightDomChildren = [];
|
this.lightDomChildren = [];
|
||||||
this.shadowDomChildren = [];
|
this.shadowDomChildren = [];
|
||||||
this.changeDetectorRef = new ChangeDetectorRef(this);
|
this.ref = new ChangeDetectorRef(this);
|
||||||
this.mode = null;
|
this.mode = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,6 +80,10 @@ export class AbstractChangeDetector extends ChangeDetector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsCheckOnce() {
|
||||||
|
this.mode = CHECK_ONCE;
|
||||||
|
}
|
||||||
|
|
||||||
markPathToRootAsCheckOnce() {
|
markPathToRootAsCheckOnce() {
|
||||||
var c = this;
|
var c = this;
|
||||||
while(isPresent(c) && c.mode != DETACHED) {
|
while(isPresent(c) && c.mode != DETACHED) {
|
||||||
|
|
|
@ -32,6 +32,10 @@ export class BindingRecord {
|
||||||
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
|
return isPresent(this.directiveRecord) && this.directiveRecord.callOnChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isOnPushChangeDetection() {
|
||||||
|
return isPresent(this.directiveRecord) && this.directiveRecord.isOnPushChangeDetection();
|
||||||
|
}
|
||||||
|
|
||||||
isDirective() {
|
isDirective() {
|
||||||
return this.mode === DIRECTIVE;
|
return this.mode === DIRECTIVE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ var PIPE_REGISTRY_ACCESSOR = "this.pipeRegistry";
|
||||||
var PROTOS_ACCESSOR = "this.protos";
|
var PROTOS_ACCESSOR = "this.protos";
|
||||||
var DIRECTIVES_ACCESSOR = "this.directiveRecords";
|
var DIRECTIVES_ACCESSOR = "this.directiveRecords";
|
||||||
var CONTEXT_ACCESSOR = "this.context";
|
var CONTEXT_ACCESSOR = "this.context";
|
||||||
var CHANGE_LOCAL = "change";
|
var IS_CHANGED_LOCAL = "isChanged";
|
||||||
var CHANGES_LOCAL = "changes";
|
var CHANGES_LOCAL = "changes";
|
||||||
var LOCALS_ACCESSOR = "this.locals";
|
var LOCALS_ACCESSOR = "this.locals";
|
||||||
var MODE_ACCESSOR = "this.mode";
|
var MODE_ACCESSOR = "this.mode";
|
||||||
|
@ -78,10 +78,15 @@ function pipeOnDestroyTemplate(pipeNames:List) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipeOnDestroy:string,
|
function hydrateTemplate(type:string, mode:string, fieldDefinitions:string, pipeOnDestroy:string,
|
||||||
directiveFieldNames:List<String>):string {
|
directiveFieldNames:List<String>, detectorFieldNames:List<String>):string {
|
||||||
var directiveInit = "";
|
var directiveInit = "";
|
||||||
for(var i = 0; i < directiveFieldNames.length; ++i) {
|
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 `
|
return `
|
||||||
|
@ -90,6 +95,7 @@ ${type}.prototype.hydrate = function(context, locals, directives) {
|
||||||
${CONTEXT_ACCESSOR} = context;
|
${CONTEXT_ACCESSOR} = context;
|
||||||
${LOCALS_ACCESSOR} = locals;
|
${LOCALS_ACCESSOR} = locals;
|
||||||
${directiveInit}
|
${directiveInit}
|
||||||
|
${detectorInit}
|
||||||
}
|
}
|
||||||
${type}.prototype.dehydrate = function() {
|
${type}.prototype.dehydrate = function() {
|
||||||
${pipeOnDestroy}
|
${pipeOnDestroy}
|
||||||
|
@ -128,7 +134,7 @@ function detectChangesBodyTemplate(localDefinitions:string, changeDefinitions:st
|
||||||
${localDefinitions}
|
${localDefinitions}
|
||||||
${changeDefinitions}
|
${changeDefinitions}
|
||||||
var ${TEMP_LOCAL};
|
var ${TEMP_LOCAL};
|
||||||
var ${CHANGE_LOCAL};
|
var ${IS_CHANGED_LOCAL} = false;
|
||||||
var ${CURRENT_PROTO};
|
var ${CURRENT_PROTO};
|
||||||
var ${CHANGES_LOCAL} = null;
|
var ${CHANGES_LOCAL} = null;
|
||||||
|
|
||||||
|
@ -208,6 +214,7 @@ function updateDirectiveTemplate(oldValue:string, newValue:string, directiveProp
|
||||||
return `
|
return `
|
||||||
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
|
if(throwOnChange) ${UTIL}.throwOnChange(${CURRENT_PROTO}, ${UTIL}.simpleChange(${oldValue}, ${newValue}));
|
||||||
${directiveProperty} = ${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 {
|
export class ChangeDetectorJITGenerator {
|
||||||
typeName:string;
|
typeName:string;
|
||||||
|
@ -285,22 +308,32 @@ export class ChangeDetectorJITGenerator {
|
||||||
genHydrate():string {
|
genHydrate():string {
|
||||||
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
|
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
|
||||||
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
|
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
|
||||||
pipeOnDestroyTemplate(this.getNonNullPipeNames()), this.getDirectiveFieldNames());
|
pipeOnDestroyTemplate(this.getNonNullPipeNames()),
|
||||||
|
this.getDirectiveFieldNames(), this.getDetectorFieldNames());
|
||||||
}
|
}
|
||||||
|
|
||||||
getDirectiveFieldNames():List<string> {
|
getDirectiveFieldNames():List<string> {
|
||||||
return this.directiveRecords.map((d) => this.getDirective(d));
|
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) {
|
getDirective(d:DirectiveRecord) {
|
||||||
return `this.directive_${d.name}`;
|
return `this.directive_${d.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDetector(d:DirectiveRecord) {
|
||||||
|
return `this.detector_${d.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
genFieldDefinitions() {
|
genFieldDefinitions() {
|
||||||
var fields = [];
|
var fields = [];
|
||||||
fields = fields.concat(this.fieldNames);
|
fields = fields.concat(this.fieldNames);
|
||||||
fields = fields.concat(this.getNonNullPipeNames());
|
fields = fields.concat(this.getNonNullPipeNames());
|
||||||
fields = fields.concat(this.getDirectiveFieldNames());
|
fields = fields.concat(this.getDirectiveFieldNames());
|
||||||
|
fields = fields.concat(this.getDetectorFieldNames());
|
||||||
return fieldDefinitionsTemplate(fields);
|
return fieldDefinitionsTemplate(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,11 +395,11 @@ export class ChangeDetectorJITGenerator {
|
||||||
var change = this.changeNames[r.selfIndex];
|
var change = this.changeNames[r.selfIndex];
|
||||||
|
|
||||||
var pipe = this.pipeNames[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 update = this.genUpdateDirectiveOrElement(r);
|
||||||
var addToChanges = this.genAddToChanges(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,
|
return pipeCheckTemplate(r.selfIndex - 1, context, cdRef, pipe, r.name, oldValue, newValue, change,
|
||||||
update, addToChanges, lastInDirective);
|
update, addToChanges, lastInDirective);
|
||||||
|
@ -380,7 +413,7 @@ export class ChangeDetectorJITGenerator {
|
||||||
|
|
||||||
var update = this.genUpdateDirectiveOrElement(r);
|
var update = this.genUpdateDirectiveOrElement(r);
|
||||||
var addToChanges = this.genAddToChanges(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,
|
var check = referenceCheckTemplate(r.selfIndex - 1, assignment, oldValue, newValue, change,
|
||||||
update, addToChanges, lastInDirective);
|
update, addToChanges, lastInDirective);
|
||||||
|
@ -471,6 +504,12 @@ export class ChangeDetectorJITGenerator {
|
||||||
return r.bindingRecord.callOnChange() ? addToChangesTemplate(oldValue, newValue) : "";
|
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{
|
genNotifyOnChanges(r:ProtoRecord):string{
|
||||||
var br = r.bindingRecord;
|
var br = r.bindingRecord;
|
||||||
if (r.lastInDirective && br.callOnChange()) {
|
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 {
|
genArgs(r:ProtoRecord):string {
|
||||||
return r.args.map((arg) => this.localNames[arg]).join(", ");
|
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 {
|
export class DirectiveRecord {
|
||||||
elementIndex:number;
|
elementIndex:number;
|
||||||
directiveIndex:number;
|
directiveIndex:number;
|
||||||
callOnAllChangesDone:boolean;
|
callOnAllChangesDone:boolean;
|
||||||
callOnChange:boolean;
|
callOnChange:boolean;
|
||||||
|
changeDetection:string;
|
||||||
|
|
||||||
constructor(elementIndex:number, directiveIndex:number,
|
constructor(elementIndex:number, directiveIndex:number,
|
||||||
callOnAllChangesDone:boolean,
|
callOnAllChangesDone:boolean, callOnChange:boolean, changeDetection:string) {
|
||||||
callOnChange:boolean) {
|
|
||||||
this.elementIndex = elementIndex;
|
this.elementIndex = elementIndex;
|
||||||
this.directiveIndex = directiveIndex;
|
this.directiveIndex = directiveIndex;
|
||||||
this.callOnAllChangesDone = callOnAllChangesDone;
|
this.callOnAllChangesDone = callOnAllChangesDone;
|
||||||
this.callOnChange = callOnChange;
|
this.callOnChange = callOnChange;
|
||||||
|
this.changeDetection = changeDetection;
|
||||||
|
}
|
||||||
|
|
||||||
|
isOnPushChangeDetection():boolean {
|
||||||
|
return StringWrapper.equals(this.changeDetection, ON_PUSH);
|
||||||
}
|
}
|
||||||
|
|
||||||
get name() {
|
get name() {
|
||||||
|
|
|
@ -95,19 +95,31 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
var protos:List<ProtoRecord> = this.protos;
|
var protos:List<ProtoRecord> = this.protos;
|
||||||
|
|
||||||
var changes = null;
|
var changes = null;
|
||||||
|
var isChanged = false;
|
||||||
for (var i = 0; i < protos.length; ++i) {
|
for (var i = 0; i < protos.length; ++i) {
|
||||||
var proto:ProtoRecord = protos[i];
|
var proto:ProtoRecord = protos[i];
|
||||||
|
var bindingRecord = proto.bindingRecord;
|
||||||
|
var directiveRecord = bindingRecord.directiveRecord;
|
||||||
|
|
||||||
var change = this._check(proto);
|
var change = this._check(proto);
|
||||||
if (isPresent(change)) {
|
if (isPresent(change)) {
|
||||||
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
if (throwOnChange) ChangeDetectionUtil.throwOnChange(proto, change);
|
||||||
this._updateDirectiveOrElement(change, proto.bindingRecord);
|
this._updateDirectiveOrElement(change, bindingRecord);
|
||||||
changes = this._addChange(proto.bindingRecord, change, changes);
|
isChanged = true;
|
||||||
|
changes = this._addChange(bindingRecord, change, changes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proto.lastInDirective && isPresent(changes)) {
|
if (proto.lastInDirective) {
|
||||||
this._directive(proto.bindingRecord.directiveRecord).onChange(changes);
|
if (isPresent(changes)) {
|
||||||
changes = null;
|
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) {
|
for (var i = dirs.length - 1; i >= 0; --i) {
|
||||||
var dir = dirs[i];
|
var dir = dirs[i];
|
||||||
if (dir.callOnAllChangesDone) {
|
if (dir.callOnAllChangesDone) {
|
||||||
this._directive(dir).onAllChangesDone();
|
this._getDirectiveFor(dir).onAllChangesDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +138,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
if (isBlank(bindingRecord.directiveRecord)) {
|
if (isBlank(bindingRecord.directiveRecord)) {
|
||||||
this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue);
|
this.dispatcher.notifyOnBinding(bindingRecord, change.currentValue);
|
||||||
} else {
|
} 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) {
|
_getDirectiveFor(directive:DirectiveRecord) {
|
||||||
return this.directives.directive(directive);
|
return this.directives.getDirectiveFor(directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDetectorFor(directive:DirectiveRecord) {
|
||||||
|
return this.directives.getDetectorFor(directive);
|
||||||
}
|
}
|
||||||
|
|
||||||
_check(proto:ProtoRecord) {
|
_check(proto:ProtoRecord) {
|
||||||
|
@ -249,7 +265,7 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
||||||
//
|
//
|
||||||
// In the future, pipes declared in the bind configuration should
|
// In the future, pipes declared in the bind configuration should
|
||||||
// be able to access the changeDetectorRef of that component.
|
// 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);
|
var pipe = this.pipeRegistry.get(proto.name, context, cdr);
|
||||||
this._writePipe(proto, pipe);
|
this._writePipe(proto, pipe);
|
||||||
return pipe;
|
return pipe;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {ABSTRACT, CONST, normalizeBlank, isPresent} from 'angular2/src/facade/lang';
|
import {ABSTRACT, CONST, normalizeBlank, isPresent} from 'angular2/src/facade/lang';
|
||||||
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
import {ListWrapper, List} from 'angular2/src/facade/collection';
|
||||||
import {Injectable} from 'angular2/di';
|
import {Injectable} from 'angular2/di';
|
||||||
|
import {DEFAULT} from 'angular2/change_detection';
|
||||||
|
|
||||||
// type StringMap = {[idx: string]: string};
|
// type StringMap = {[idx: string]: string};
|
||||||
|
|
||||||
|
@ -553,7 +554,7 @@ export class Component extends Directive {
|
||||||
hostListeners,
|
hostListeners,
|
||||||
injectables,
|
injectables,
|
||||||
lifecycle,
|
lifecycle,
|
||||||
changeDetection
|
changeDetection = DEFAULT
|
||||||
}:{
|
}:{
|
||||||
selector:string,
|
selector:string,
|
||||||
properties:Object,
|
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 {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||||
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
||||||
import {Directive, Component, onChange, onDestroy, onAllChangesDone} from 'angular2/src/core/annotations/annotations';
|
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';
|
import {QueryList} from './query_list';
|
||||||
|
|
||||||
var _MAX_DIRECTIVE_CONSTRUCTION_COUNTER = 10;
|
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 {
|
static createFromBinding(b:Binding, annotation:Directive):DirectiveBinding {
|
||||||
var rb = b.resolve();
|
var rb = b.resolve();
|
||||||
var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom);
|
var deps = ListWrapper.map(rb.dependencies, DirectiveDependency.createFrom);
|
||||||
|
@ -298,13 +307,13 @@ export class PreBuiltObjects {
|
||||||
view:viewModule.AppView;
|
view:viewModule.AppView;
|
||||||
element:NgElement;
|
element:NgElement;
|
||||||
viewContainer:ViewContainer;
|
viewContainer:ViewContainer;
|
||||||
changeDetectorRef:ChangeDetectorRef;
|
changeDetector:ChangeDetector;
|
||||||
constructor(view, element:NgElement, viewContainer:ViewContainer,
|
constructor(view, element:NgElement, viewContainer:ViewContainer,
|
||||||
changeDetectorRef:ChangeDetectorRef) {
|
changeDetector:ChangeDetector) {
|
||||||
this.view = view;
|
this.view = view;
|
||||||
this.element = element;
|
this.element = element;
|
||||||
this.viewContainer = viewContainer;
|
this.viewContainer = viewContainer;
|
||||||
this.changeDetectorRef = changeDetectorRef;
|
this.changeDetector = changeDetector;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -603,6 +612,10 @@ export class ElementInjector extends TreeNode {
|
||||||
return this._preBuiltObjects.element;
|
return this._preBuiltObjects.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getChangeDetector() {
|
||||||
|
return this._preBuiltObjects.changeDetector;
|
||||||
|
}
|
||||||
|
|
||||||
getComponent() {
|
getComponent() {
|
||||||
if (this._proto._binding0IsComponent) {
|
if (this._proto._binding0IsComponent) {
|
||||||
return this._obj0;
|
return this._obj0;
|
||||||
|
@ -885,7 +898,7 @@ export class ElementInjector extends TreeNode {
|
||||||
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
|
if (keyId === staticKeys.viewId) return this._preBuiltObjects.view;
|
||||||
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
|
if (keyId === staticKeys.ngElementId) return this._preBuiltObjects.element;
|
||||||
if (keyId === staticKeys.viewContainerId) return this._preBuiltObjects.viewContainer;
|
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
|
//TODO add other objects as needed
|
||||||
return _undefined;
|
return _undefined;
|
||||||
|
|
|
@ -256,11 +256,16 @@ export class AppView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
directive(directive:DirectiveRecord) {
|
getDirectiveFor(directive:DirectiveRecord) {
|
||||||
var elementInjector:ElementInjector = this.elementInjectors[directive.elementIndex];
|
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDetectorFor(directive:DirectiveRecord) {
|
||||||
|
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||||
|
return elementInjector.getChangeDetector();
|
||||||
|
}
|
||||||
|
|
||||||
setDynamicComponentChildView(boundElementIndex, view:AppView) {
|
setDynamicComponentChildView(boundElementIndex, view:AppView) {
|
||||||
if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) {
|
if (!this.proto.elementBinders[boundElementIndex].hasDynamicComponent()) {
|
||||||
throw new BaseException(`There is no dynamic component directive at element ${boundElementIndex}`);
|
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)) {
|
if (!MapWrapper.contains(this._directiveRecordsMap, id)) {
|
||||||
var binding = protoElementInjector.getDirectiveBindingAtIndex(directiveIndex);
|
var binding = protoElementInjector.getDirectiveBindingAtIndex(directiveIndex);
|
||||||
|
var changeDetection = binding.changeDetection;
|
||||||
|
|
||||||
MapWrapper.set(this._directiveRecordsMap, id,
|
MapWrapper.set(this._directiveRecordsMap, id,
|
||||||
new DirectiveRecord(elementInjectorIndex, directiveIndex,
|
new DirectiveRecord(elementInjectorIndex, directiveIndex,
|
||||||
binding.callOnAllChangesDone, binding.callOnChange));
|
binding.callOnAllChangesDone, binding.callOnChange, changeDetection));
|
||||||
}
|
}
|
||||||
|
|
||||||
return MapWrapper.get(this._directiveRecordsMap, id);
|
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 {NgElement} from 'angular2/src/core/compiler/ng_element';
|
||||||
import * as vcModule from './view_container';
|
import * as vcModule from './view_container';
|
||||||
import * as viewModule from './view';
|
import * as viewModule from './view';
|
||||||
import {ChangeDetectorRef} from 'angular2/change_detection';
|
|
||||||
|
|
||||||
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
|
// TODO(tbosch): Make this an OpaqueToken as soon as our transpiler supports this!
|
||||||
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
|
export const VIEW_POOL_CAPACITY = 'ViewFactory.viewPoolCapacity';
|
||||||
|
@ -77,12 +76,11 @@ export class ViewFactory {
|
||||||
elementInjectors[binderIdx] = elementInjector;
|
elementInjectors[binderIdx] = elementInjector;
|
||||||
|
|
||||||
// componentChildViews
|
// componentChildViews
|
||||||
var changeDetectorRef = null;
|
var childChangeDetector = null;
|
||||||
if (binder.hasStaticComponent()) {
|
if (binder.hasStaticComponent()) {
|
||||||
var childView = this._createView(binder.nestedProtoView);
|
var childView = this._createView(binder.nestedProtoView);
|
||||||
changeDetector.addShadowDomChild(childView.changeDetector);
|
childChangeDetector = childView.changeDetector;
|
||||||
|
changeDetector.addShadowDomChild(childChangeDetector);
|
||||||
changeDetectorRef = new ChangeDetectorRef(childView.changeDetector);
|
|
||||||
|
|
||||||
componentChildViews[binderIdx] = childView;
|
componentChildViews[binderIdx] = childView;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +95,7 @@ export class ViewFactory {
|
||||||
// preBuiltObjects
|
// preBuiltObjects
|
||||||
if (isPresent(elementInjector)) {
|
if (isPresent(elementInjector)) {
|
||||||
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer,
|
preBuiltObjects[binderIdx] = new eli.PreBuiltObjects(view, new NgElement(view, binderIdx), viewContainer,
|
||||||
changeDetectorRef);
|
childChangeDetector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function dirs(directives:List) {
|
function dirs(directives:List) {
|
||||||
return new FakeDirectives(directives);
|
return new FakeDirectives(directives, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertLocalsToVariableBindings(locals) {
|
function convertLocalsToVariableBindings(locals) {
|
||||||
|
@ -246,9 +246,9 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("updating directives", () => {
|
describe("updating directives", () => {
|
||||||
var dirRecord1 = new DirectiveRecord(0, 0, true, true);
|
var dirRecord1 = new DirectiveRecord(0, 0, true, true, DEFAULT);
|
||||||
var dirRecord2 = new DirectiveRecord(0, 1, true, true);
|
var dirRecord2 = new DirectiveRecord(0, 1, true, true, DEFAULT);
|
||||||
var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false);
|
var dirRecordNoCallbacks = new DirectiveRecord(0, 0, false, false, DEFAULT);
|
||||||
|
|
||||||
function updateA(exp:string, dirRecord) {
|
function updateA(exp:string, dirRecord) {
|
||||||
return BindingRecord.createForDirective(ast(exp), "a", (o,v) => o.a = v, 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);
|
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", () => {
|
describe("markPathToRootAsCheckOnce", () => {
|
||||||
|
@ -664,7 +702,7 @@ export function main() {
|
||||||
expect(pipe.destroyCalled).toEqual(true);
|
expect(pipe.destroyCalled).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should inject the binding propagation configuration " +
|
it("should inject the ChangeDetectorRef " +
|
||||||
"of the encompassing component into a pipe", () => {
|
"of the encompassing component into a pipe", () => {
|
||||||
|
|
||||||
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe());
|
var registry = new FakePipeRegistry('pipe', () => new IdentityPipe());
|
||||||
|
@ -673,7 +711,7 @@ export function main() {
|
||||||
|
|
||||||
cd.detectChanges();
|
cd.detectChanges();
|
||||||
|
|
||||||
expect(registry.cdRef).toBe(cd.changeDetectorRef);
|
expect(registry.cdRef).toBe(cd.ref);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -853,14 +891,20 @@ class TestData {
|
||||||
|
|
||||||
class FakeDirectives {
|
class FakeDirectives {
|
||||||
directives:List;
|
directives:List;
|
||||||
|
detectors:List;
|
||||||
|
|
||||||
constructor(directives:List) {
|
constructor(directives:List, detectors:List) {
|
||||||
this.directives = directives;
|
this.directives = directives;
|
||||||
|
this.detectors = detectors;
|
||||||
}
|
}
|
||||||
|
|
||||||
directive(directiveRecord:DirectiveRecord) {
|
getDirectiveFor(directiveRecord:DirectiveRecord) {
|
||||||
return this.directives[directiveRecord.directiveIndex];
|
return this.directives[directiveRecord.directiveIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getDetectorFor(directiveRecord:DirectiveRecord) {
|
||||||
|
return this.detectors[directiveRecord.directiveIndex];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestDispatcher extends ChangeDispatcher {
|
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 {ViewContainer} from 'angular2/src/core/compiler/view_container';
|
||||||
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
import {NgElement} from 'angular2/src/core/compiler/ng_element';
|
||||||
import {Directive} from 'angular2/src/core/annotations/annotations';
|
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 {ViewRef, Renderer, EventBinding} from 'angular2/src/render/api';
|
||||||
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
||||||
|
|
||||||
|
@ -621,10 +621,10 @@ export function main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return changeDetectorRef', function () {
|
it('should return changeDetectorRef', function () {
|
||||||
var config = new ChangeDetectorRef(null);
|
var cd = new DynamicChangeDetector(null, null, null, [], []);
|
||||||
var inj = injector([], null, null, new PreBuiltObjects(null, null, null, config));
|
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", () => {
|
describe("ON_PUSH components", () => {
|
||||||
it("can be used to disable the change detection of the component's template",
|
it("should use ChangeDetectorRef to manually request a check",
|
||||||
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
inject([TestBed, AsyncTestCompleter], (tb, async) => {
|
||||||
|
|
||||||
tb.overrideView(MyComp, new View({
|
tb.overrideView(MyComp, new View({
|
||||||
template: '<push-cmp #cmp></push-cmp>',
|
template: '<push-cmp-with-ref #cmp></push-cmp-with-ref>',
|
||||||
directives: [[[PushBasedComp]]]
|
directives: [[[PushCmpWithRef]]]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
tb.createView(MyComp, {context: ctx}).then((view) => {
|
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({
|
tb.overrideView(MyComp, new View({
|
||||||
template: '<push-cmp [prop]="ctxProp" #cmp></push-cmp>',
|
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) => {
|
tb.createView(MyComp, {context: ctx}).then((view) => {
|
||||||
|
@ -800,7 +823,29 @@ class MyDir {
|
||||||
changeDetection:ON_PUSH
|
changeDetection:ON_PUSH
|
||||||
})
|
})
|
||||||
@View({template: '{{field}}'})
|
@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;
|
numberOfChecks:number;
|
||||||
ref:ChangeDetectorRef;
|
ref:ChangeDetectorRef;
|
||||||
prop;
|
prop;
|
||||||
|
|
|
@ -12,7 +12,8 @@ import {
|
||||||
dynamicChangeDetection,
|
dynamicChangeDetection,
|
||||||
jitChangeDetection,
|
jitChangeDetection,
|
||||||
BindingRecord,
|
BindingRecord,
|
||||||
DirectiveRecord
|
DirectiveRecord,
|
||||||
|
DEFAULT
|
||||||
} from 'angular2/change_detection';
|
} from 'angular2/change_detection';
|
||||||
|
|
||||||
|
|
||||||
|
@ -191,7 +192,7 @@ function setUpChangeDetection(changeDetection:ChangeDetection, iterations, objec
|
||||||
|
|
||||||
var proto = changeDetection.createProtoChangeDetector("proto");
|
var proto = changeDetection.createProtoChangeDetector("proto");
|
||||||
|
|
||||||
var directiveRecord = new DirectiveRecord(0, 0, false, false);
|
var directiveRecord = new DirectiveRecord(0, 0, false, false, DEFAULT);
|
||||||
var bindings = [
|
var bindings = [
|
||||||
BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord),
|
BindingRecord.createForDirective(parser.parseBinding('field0', null), "field0", reflector.setter("field0"), directiveRecord),
|
||||||
BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),
|
BindingRecord.createForDirective(parser.parseBinding('field1', null), "field1", reflector.setter("field1"), directiveRecord),
|
||||||
|
@ -306,7 +307,7 @@ class FakeDirectives {
|
||||||
this.targetObj = targetObj;
|
this.targetObj = targetObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
directive(record) {
|
getDirectiveFor(record) {
|
||||||
return this.targetObj;
|
return this.targetObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue