feat(change_detection): added changeDetection to Component

This commit is contained in:
vsavkin
2015-03-30 16:54:10 -07:00
parent a11f683e7b
commit 514ba54282
20 changed files with 210 additions and 194 deletions
@@ -1,7 +1,8 @@
import {isPresent} from 'angular2/src/facade/lang';
import {List, ListWrapper} from 'angular2/src/facade/collection';
import {BindingPropagationConfig} from './binding_propagation_config';
import {ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
import {ChangeDetector} from './interfaces';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
export class AbstractChangeDetector extends ChangeDetector {
lightDomChildren:List;
@@ -15,7 +16,7 @@ export class AbstractChangeDetector extends ChangeDetector {
this.lightDomChildren = [];
this.shadowDomChildren = [];
this.bindingPropagationConfig = new BindingPropagationConfig(this);
this.mode = CHECK_ALWAYS;
this.mode = null;
}
addChild(cd:ChangeDetector) {
@@ -1,4 +1,5 @@
import {ChangeDetector, CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './interfaces';
import {ChangeDetector} from './interfaces';
import {CHECK_ONCE, DETACHED, CHECK_ALWAYS} from './constants';
/**
* @publicModule angular2/angular2
@@ -1,7 +1,7 @@
library change_detectoin.change_detection_jit_generator;
class ChangeDetectorJITGenerator {
ChangeDetectorJITGenerator(typeName, records, directiveMementos) {
ChangeDetectorJITGenerator(typeName, strategy, records, directiveMementos) {
}
generate() {
@@ -24,74 +24,10 @@ import {
* that "emulates" what the developer would write by hand to implement the same
* kind of behaviour.
*
* For example: An expression `address.city` will result in the following class:
*
* var ChangeDetector0 = function ChangeDetector0(dispatcher, protos) {
* AbstractChangeDetector.call(this);
* this.dispatcher = dispatcher;
* this.protos = protos;
*
* this.context = ChangeDetectionUtil.unitialized();
* this.address0 = ChangeDetectionUtil.unitialized();
* this.city1 = ChangeDetectionUtil.unitialized();
* }
* ChangeDetector0.prototype = Object.create(AbstractChangeDetector.prototype);
*
* ChangeDetector0.prototype.detectChangesInRecords = function(throwOnChange) {
* var address0;
* var city1;
* var change;
* var changes = null;
* var temp;
* var context = this.context;
*
* address0 = context.address;
* if (address0 !== this.address0) {
* this.address0 = address0;
* }
*
* city1 = address0.city;
* if (city1 !== this.city1) {
* changes = ChangeDetectionUtil.addRecord(changes,
* ChangeDetectionUtil.simpleChangeRecord(this.protos[1].bindingMemento, this.city1, city1));
* this.city1 = city1;
* }
*
* if (changes.length > 0) {
* if(throwOnChange) ChangeDetectionUtil.throwOnChange(this.protos[1], changes[0]);
* this.dispatcher.onRecordChange('address.city', changes);
* changes = null;
* }
* }
*
* ChangeDetector0.prototype.callOnAllChangesDone = function() {}
*
* ChangeDetector0.prototype.hydrate = function(context, locals) {
* this.context = context;
* this.locals = locals;
* }
*
* ChangeDetector0.prototype.dehydrate = function(context) {
* this.context = ChangeDetectionUtil.unitialized();
* this.address0 = ChangeDetectionUtil.unitialized();
* this.city1 = ChangeDetectionUtil.unitialized();
* this.locals = null;
* }
*
* ChangeDetector0.prototype.hydrated = function() {
* return this.context !== ChangeDetectionUtil.unitialized();
* }
*
* return ChangeDetector0;
*
*
* The only thing the generated class depends on is the super class AbstractChangeDetector.
*
* The implementation comprises two parts:
* * ChangeDetectorJITGenerator has the logic of how everything fits together.
* * template functions (e.g., constructorTemplate) define what code is generated.
*/
var ABSTRACT_CHANGE_DETECTOR = "AbstractChangeDetector";
var UTIL = "ChangeDetectionUtil";
var DISPATCHER_ACCESSOR = "this.dispatcher";
@@ -102,6 +38,7 @@ var CONTEXT_ACCESSOR = "this.context";
var CHANGE_LOCAL = "change";
var CHANGES_LOCAL = "changes";
var LOCALS_ACCESSOR = "this.locals";
var MODE_ACCESSOR = "this.mode";
var TEMP_LOCAL = "temp";
function typeTemplate(type:string, cons:string, detectChanges:string,
@@ -137,9 +74,10 @@ function pipeOnDestroyTemplate(pipeNames:List) {
return pipeNames.map((p) => `${p}.onDestroy()`).join("\n");
}
function hydrateTemplate(type:string, fieldsDefinitions:string, pipeOnDestroy:string):string {
function hydrateTemplate(type:string, mode:string, fieldsDefinitions:string, pipeOnDestroy:string):string {
return `
${type}.prototype.hydrate = function(context, locals) {
${MODE_ACCESSOR} = "${mode}";
${CONTEXT_ACCESSOR} = context;
${LOCALS_ACCESSOR} = locals;
}
@@ -265,13 +203,15 @@ export class ChangeDetectorJITGenerator {
typeName:string;
records:List<ProtoRecord>;
directiveMementos:List;
localNames:List<String>;
changeNames:List<String>;
fieldNames:List<String>;
pipeNames:List<String>;
localNames:List<string>;
changeNames:List<string>;
fieldNames:List<string>;
pipeNames:List<string>;
changeDetectionStrategy:stirng;
constructor(typeName:string, records:List<ProtoRecord>, directiveMementos:List) {
constructor(typeName:string, changeDetectionStrategy:string, records:List<ProtoRecord>, directiveMementos:List) {
this.typeName = typeName;
this.changeDetectionStrategy = changeDetectionStrategy;
this.records = records;
this.directiveMementos = directiveMementos;
@@ -281,7 +221,7 @@ export class ChangeDetectorJITGenerator {
this.pipeNames = this.getPipeNames(this.localNames);
}
getLocalNames(records:List<ProtoRecord>):List<String> {
getLocalNames(records:List<ProtoRecord>):List<string> {
var index = 0;
var names = records.map((r) => {
var sanitizedName = r.name.replace(new RegExp("\\W", "g"), '');
@@ -290,15 +230,15 @@ export class ChangeDetectorJITGenerator {
return ["context"].concat(names);
}
getChangeNames(localNames:List<String>):List<String> {
getChangeNames(localNames:List<string>):List<string> {
return localNames.map((n) => `change_${n}`);
}
getFieldNames(localNames:List<String>):List<String> {
getFieldNames(localNames:List<string>):List<string> {
return localNames.map((n) => `this.${n}`);
}
getPipeNames(localNames:List<String>):List<String> {
getPipeNames(localNames:List<string>):List<string> {
return localNames.map((n) => `this.${n}_pipe`);
}
@@ -314,7 +254,8 @@ export class ChangeDetectorJITGenerator {
}
genHydrate():string {
return hydrateTemplate(this.typeName, this.genFieldDefinitions(),
var mode = ChangeDetectionUtil.changeDetectionMode(this.changeDetectionStrategy);
return hydrateTemplate(this.typeName, mode, this.genFieldDefinitions(),
pipeOnDestroyTemplate(this.getNonNullPipeNames()));
}
@@ -325,7 +266,7 @@ export class ChangeDetectorJITGenerator {
return fieldDefinitionsTemplate(fields);
}
getNonNullPipeNames():List<String> {
getNonNullPipeNames():List<string> {
var pipes = [];
this.records.forEach((r) => {
if (r.mode === RECORD_TYPE_PIPE || r.mode === RECORD_TYPE_BINDING_PIPE) {
@@ -3,7 +3,8 @@ import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/faca
import {ProtoRecord} from './proto_record';
import {ExpressionChangedAfterItHasBeenChecked} from './exceptions';
import {NO_CHANGE} from './pipes/pipe';
import {ChangeRecord, ChangeDetector, CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED} from './interfaces';
import {ChangeRecord, ChangeDetector} from './interfaces';
import {CHECK_ALWAYS, CHECK_ONCE, CHECKED, DETACHED, ON_PUSH} from './constants';
export var uninitialized = new Object();
@@ -163,6 +164,10 @@ export class ChangeDetectionUtil {
return _changeRecord(memento, _simpleChange(previousValue, currentValue));
}
static changeDetectionMode(strategy:string) {
return strategy == ON_PUSH ? CHECK_ONCE : CHECK_ALWAYS;
}
static addRecord(updatedRecords:List, changeRecord:ChangeRecord):List {
if (isBlank(updatedRecords)) {
updatedRecords = _singleElementList;
+35
View File
@@ -0,0 +1,35 @@
//TODO:vsavkin Use enums after switching to TypeScript
/**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED.
*/
export const CHECK_ONCE="CHECK_ONCE";
/**
* CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS.
*/
export const CHECKED="CHECKED";
/**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS.
*/
export const CHECK_ALWAYS="ALWAYS_CHECK";
/**
* DETACHED means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
export const DETACHED="DETACHED";
/**
* ON_PUSH means that the change detector's mode will be set to CHECK_ONCE during hydration.
*/
export const ON_PUSH = "ON_PUSH";
/**
* DEFAULT means that the change detector's mode will be set to CHECK_ALWAYS during hydration.
*/
export const DEFAULT = "DEFAULT";
@@ -35,8 +35,10 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
protos:List<ProtoRecord>;
directiveMementos:List;
changeControlStrategy:string;
constructor(dispatcher:any, pipeRegistry:PipeRegistry, protoRecords:List<ProtoRecord>, directiveMementos:List) {
constructor(changeControlStrategy:string, dispatcher:any, pipeRegistry:PipeRegistry,
protoRecords:List<ProtoRecord>, directiveMementos:List) {
super();
this.dispatcher = dispatcher;
this.pipeRegistry = pipeRegistry;
@@ -54,9 +56,11 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
this.protos = protoRecords;
this.directiveMementos = directiveMementos;
this.changeControlStrategy = changeControlStrategy;
}
hydrate(context:any, locals:any) {
this.mode = ChangeDetectionUtil.changeDetectionMode(this.changeControlStrategy);
this.values[0] = context;
this.locals = locals;
}
+14 -25
View File
@@ -1,5 +1,19 @@
import {List} from 'angular2/src/facade/collection';
import {Locals} from './parser/locals';
import {AST} from './parser/ast';
export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMemento:List):ChangeDetector{
return null;
}
}
export class ChangeDetection {
createProtoChangeDetector(name:string, changeControlStrategy:string):ProtoChangeDetector{
return null;
}
}
export class ChangeRecord {
bindingMemento:any;
@@ -20,31 +34,6 @@ export class ChangeRecord {
}
}
/**
* CHECK_ONCE means that after calling detectChanges the mode of the change detector
* will become CHECKED.
*/
export const CHECK_ONCE="CHECK_ONCE";
/**
* CHECKED means that the change detector should be skipped until its mode changes to
* CHECK_ONCE or CHECK_ALWAYS.
*/
export const CHECKED="CHECKED";
/**
* CHECK_ALWAYS means that after calling detectChanges the mode of the change detector
* will remain CHECK_ALWAYS.
*/
export const CHECK_ALWAYS="ALWAYS_CHECK";
/**
* DETACHED means that the change detector sub tree is not a part of the main tree and
* should be skipped.
*/
export const DETACHED="DETACHED";
export class ChangeDispatcher {
onRecordChange(directiveMemento, records:List<ChangeRecord>) {}
}
@@ -22,7 +22,7 @@ import {
PrefixNot
} from './parser/ast';
import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
import {ChangeRecord, ChangeDispatcher, ChangeDetector, ProtoChangeDetector} from './interfaces';
import {ChangeDetectionUtil} from './change_detection_util';
import {DynamicChangeDetector} from './dynamic_change_detector';
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
@@ -45,13 +45,6 @@ import {
RECORD_TYPE_INTERPOLATE
} from './proto_record';
export class ProtoChangeDetector {
addAst(ast:AST, bindingMemento:any, directiveMemento:any = null){}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMemento:List):ChangeDetector{
return null;
}
}
export class BindingRecord {
ast:AST;
bindingMemento:any;
@@ -67,15 +60,18 @@ export class BindingRecord {
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
_pipeRegistry:PipeRegistry;
_records:List<ProtoRecord>;
_changeControlStrategy:string;
constructor(pipeRegistry:PipeRegistry) {
constructor(pipeRegistry:PipeRegistry, changeControlStrategy:string) {
super();
this._pipeRegistry = pipeRegistry;
this._changeControlStrategy = changeControlStrategy;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
this._createRecordsIfNecessary(bindingRecords, variableBindings);
return new DynamicChangeDetector(dispatcher, this._pipeRegistry, this._records, directiveMementos);
return new DynamicChangeDetector(this._changeControlStrategy, dispatcher,
this._pipeRegistry, this._records, directiveMementos);
}
_createRecordsIfNecessary(bindingRecords:List, variableBindings:List) {
@@ -93,11 +89,13 @@ var _jitProtoChangeDetectorClassCounter:number = 0;
export class JitProtoChangeDetector extends ProtoChangeDetector {
_factory:Function;
_pipeRegistry;
_changeControlStrategy:string;
constructor(pipeRegistry) {
constructor(pipeRegistry, changeControlStrategy:string) {
super();
this._pipeRegistry = pipeRegistry;
this._factory = null;
this._changeControlStrategy = changeControlStrategy;
}
instantiate(dispatcher:any, bindingRecords:List, variableBindings:List, directiveMementos:List) {
@@ -114,7 +112,8 @@ export class JitProtoChangeDetector extends ProtoChangeDetector {
var c = _jitProtoChangeDetectorClassCounter++;
var records = coalesce(recordBuilder.records);
var typeName = `ChangeDetector${c}`;
this._factory = new ChangeDetectorJITGenerator(typeName, records, directiveMementos).generate();
this._factory = new ChangeDetectorJITGenerator(typeName, this._changeControlStrategy, records,
directiveMementos).generate();
}
}
}
+17 -3
View File
@@ -480,6 +480,17 @@ export class Directive extends Injectable {
* @publicModule angular2/annotations
*/
export class Component extends Directive {
/**
* Defines the used change detection strategy.
*
* When a component is instantiated, Angular creates a change detector, which is responsible for propagating
* the component's bindings.
*
* The changeDetection property defines if the change detection will be checked every time or only when the component
* tell it too.
*/
changeDetection:string;
/**
* Defines the set of injectable objects that are visible to a Component and its children.
*
@@ -534,13 +545,15 @@ export class Component extends Directive {
bind,
events,
services,
lifecycle
lifecycle,
changeDetection
}:{
selector:String,
selector:string,
bind:Object,
events:Object,
services:List,
lifecycle:List
lifecycle:List,
changeDetection:string
}={})
{
super({
@@ -550,6 +563,7 @@ export class Component extends Directive {
lifecycle: lifecycle
});
this.changeDetection = changeDetection;
this.services = services;
}
}
@@ -36,9 +36,9 @@ export function createDefaultSteps(
new DirectiveParser(directives),
new TextInterpolationParser(parser),
new ElementBindingMarker(),
new ProtoViewBuilder(changeDetection, shadowDomStrategy),
new ProtoViewBuilder(compiledComponent, changeDetection, shadowDomStrategy),
new ProtoElementInjectorBuilder(),
new ElementBinderBuilder(parser),
new ElementBinderBuilder(parser)
];
return steps;
@@ -8,6 +8,8 @@ import {CompileStep} from './compile_step';
import {CompileElement} from './compile_element';
import {CompileControl} from './compile_control';
import {ShadowDomStrategy} from '../shadow_dom_strategy';
import {DirectiveMetadata} from '../directive_metadata';
import {Component} from 'angular2/src/core/annotations/annotations';
/**
* Creates ProtoViews and forwards variable bindings from parent to children.
@@ -24,8 +26,12 @@ import {ShadowDomStrategy} from '../shadow_dom_strategy';
export class ProtoViewBuilder extends CompileStep {
changeDetection:ChangeDetection;
_shadowDomStrategy:ShadowDomStrategy;
constructor(changeDetection:ChangeDetection, shadowDomStrategy:ShadowDomStrategy) {
_compiledComponent:DirectiveMetadata;
constructor(compiledComponent:DirectiveMetadata,
changeDetection:ChangeDetection, shadowDomStrategy:ShadowDomStrategy) {
super();
this._compiledComponent = compiledComponent;
this._shadowDomStrategy = shadowDomStrategy;
this.changeDetection = changeDetection;
}
@@ -33,7 +39,10 @@ export class ProtoViewBuilder extends CompileStep {
process(parent:CompileElement, current:CompileElement, control:CompileControl) {
var inheritedProtoView = null;
if (current.isViewRoot) {
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy');
var componentAnnotation:Component = this._compiledComponent.annotation;
var protoChangeDetector = this.changeDetection.createProtoChangeDetector('dummy',
componentAnnotation.changeDetection);
inheritedProtoView = new ProtoView(current.element, protoChangeDetector,
this._shadowDomStrategy, this._getParentProtoView(parent));