feat(view): reimplemented property setters using change detection
This commit is contained in:
107
modules/angular2/src/core/annotations/annotations.js
vendored
107
modules/angular2/src/core/annotations/annotations.js
vendored
@ -187,8 +187,8 @@ import {DEFAULT} from 'angular2/change_detection';
|
||||
*
|
||||
*
|
||||
* A directive can also query for other child directives. Since parent directives are instantiated before child
|
||||
* directives, a directive can't simply inject the list of child directives. Instead, the directive
|
||||
* injects a {@link QueryList}, which updates its contents as children are added, removed, or moved by any
|
||||
* directives, a directive can't simply inject the list of child directives. Instead, the directive
|
||||
* injects a {@link QueryList}, which updates its contents as children are added, removed, or moved by any
|
||||
* {@link Viewport} directive such as a `for`, an `if`, or a `switch`.
|
||||
*
|
||||
* ```
|
||||
@ -199,7 +199,7 @@ import {DEFAULT} from 'angular2/change_detection';
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This directive would be instantiated with a {@link QueryList} which contains `Dependency` 4 and 6. Here, `Dependency`
|
||||
* This directive would be instantiated with a {@link QueryList} which contains `Dependency` 4 and 6. Here, `Dependency`
|
||||
* 5 would not be included, because it is not a direct child.
|
||||
*
|
||||
* ### Injecting a live collection of descendant directives
|
||||
@ -444,6 +444,29 @@ export class Directive extends Injectable {
|
||||
*/
|
||||
hostListeners:any; // StringMap
|
||||
|
||||
|
||||
/**
|
||||
* Specifies which DOM properties a directives updates.
|
||||
*
|
||||
* ## Syntax
|
||||
*
|
||||
* ```
|
||||
* @Decorator({
|
||||
* selector: 'input',
|
||||
* hostProperties: {
|
||||
* 'value': 'value'
|
||||
* }
|
||||
* })
|
||||
* class InputDecorator {
|
||||
* value:string;
|
||||
* }
|
||||
*
|
||||
* In this example every time the value property of the decorator changes, Angular will update the value property of
|
||||
* the host element.
|
||||
* ```
|
||||
*/
|
||||
hostProperties:any; // String map
|
||||
|
||||
/**
|
||||
* Specifies a set of lifecycle hostListeners in which the directive participates.
|
||||
*
|
||||
@ -457,12 +480,14 @@ export class Directive extends Injectable {
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
properties:any,
|
||||
events:List,
|
||||
hostListeners: any,
|
||||
hostProperties: any,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
@ -471,6 +496,7 @@ export class Directive extends Injectable {
|
||||
this.properties = properties;
|
||||
this.events = events;
|
||||
this.hostListeners = hostListeners;
|
||||
this.hostProperties = hostProperties;
|
||||
this.lifecycle = lifecycle;
|
||||
}
|
||||
|
||||
@ -579,20 +605,22 @@ export class Component extends Directive {
|
||||
*/
|
||||
injectables:List;
|
||||
|
||||
@CONST()
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
injectables,
|
||||
lifecycle,
|
||||
changeDetection = DEFAULT
|
||||
selector,
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
injectables,
|
||||
lifecycle,
|
||||
changeDetection = DEFAULT
|
||||
}:{
|
||||
selector:string,
|
||||
properties:Object,
|
||||
events:List,
|
||||
hostListeners:Object,
|
||||
hostListeners:any,
|
||||
hostProperties:any,
|
||||
injectables:List,
|
||||
lifecycle:List,
|
||||
changeDetection:string
|
||||
@ -603,6 +631,7 @@ export class Component extends Directive {
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
hostProperties: hostProperties,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
|
||||
@ -667,17 +696,19 @@ export class DynamicComponent extends Directive {
|
||||
|
||||
@CONST()
|
||||
constructor({
|
||||
selector,
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
injectables,
|
||||
lifecycle
|
||||
selector,
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
injectables,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
properties:Object,
|
||||
properties:any,
|
||||
events:List,
|
||||
hostListeners:Object,
|
||||
hostListeners:any,
|
||||
hostProperties:any,
|
||||
injectables:List,
|
||||
lifecycle:List
|
||||
}={}) {
|
||||
@ -686,6 +717,7 @@ export class DynamicComponent extends Directive {
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
hostProperties: hostProperties,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
|
||||
@ -767,6 +799,7 @@ export class Decorator extends Directive {
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
lifecycle,
|
||||
compileChildren = true,
|
||||
}:{
|
||||
@ -774,16 +807,18 @@ export class Decorator extends Directive {
|
||||
properties:any,
|
||||
events:List,
|
||||
hostListeners:any,
|
||||
hostProperties:any,
|
||||
lifecycle:List,
|
||||
compileChildren:boolean
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
lifecycle: lifecycle
|
||||
selector: selector,
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
hostProperties: hostProperties,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
this.compileChildren = compileChildren;
|
||||
}
|
||||
@ -889,20 +924,24 @@ export class Viewport extends Directive {
|
||||
properties,
|
||||
events,
|
||||
hostListeners,
|
||||
hostProperties,
|
||||
lifecycle
|
||||
}:{
|
||||
selector:string,
|
||||
properties:any,
|
||||
events:List,
|
||||
lifecycle:List
|
||||
selector:string,
|
||||
properties:any,
|
||||
hostListeners:any,
|
||||
hostProperties:any,
|
||||
events:List,
|
||||
lifecycle:List
|
||||
}={})
|
||||
{
|
||||
super({
|
||||
selector: selector,
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
lifecycle: lifecycle
|
||||
selector: selector,
|
||||
properties: properties,
|
||||
events: events,
|
||||
hostListeners: hostListeners,
|
||||
hostProperties: hostProperties,
|
||||
lifecycle: lifecycle
|
||||
});
|
||||
}
|
||||
}
|
||||
|
22
modules/angular2/src/core/annotations/di.js
vendored
22
modules/angular2/src/core/annotations/di.js
vendored
@ -1,28 +1,6 @@
|
||||
import {CONST} from 'angular2/src/facade/lang';
|
||||
import {DependencyAnnotation} from 'angular2/di';
|
||||
|
||||
/**
|
||||
* Specifies that a function for setting host properties should be injected.
|
||||
*
|
||||
* NOTE: This is changing pre 1.0.
|
||||
*
|
||||
* The directive can inject a property setter that would allow setting this property on the host element.
|
||||
*
|
||||
* @exportedAs angular2/annotations
|
||||
*/
|
||||
export class PropertySetter extends DependencyAnnotation {
|
||||
propName: string;
|
||||
@CONST()
|
||||
constructor(propName) {
|
||||
super();
|
||||
this.propName = propName;
|
||||
}
|
||||
|
||||
get token() {
|
||||
return Function;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that a constant attribute value should be injected.
|
||||
*
|
||||
|
@ -223,12 +223,8 @@ export class Compiler {
|
||||
renderType = renderApi.DirectiveMetadata.DECORATOR_TYPE;
|
||||
compileChildren = ann.compileChildren;
|
||||
}
|
||||
var setters = [];
|
||||
var readAttributes = [];
|
||||
ListWrapper.forEach(directiveBinding.dependencies, (dep) => {
|
||||
if (isPresent(dep.propSetterName)) {
|
||||
ListWrapper.push(setters, dep.propSetterName);
|
||||
}
|
||||
if (isPresent(dep.attributeName)) {
|
||||
ListWrapper.push(readAttributes, dep.attributeName);
|
||||
}
|
||||
@ -239,8 +235,8 @@ export class Compiler {
|
||||
selector: ann.selector,
|
||||
compileChildren: compileChildren,
|
||||
hostListeners: isPresent(ann.hostListeners) ? MapWrapper.createFromStringMap(ann.hostListeners) : null,
|
||||
hostProperties: isPresent(ann.hostProperties) ? MapWrapper.createFromStringMap(ann.hostProperties) : null,
|
||||
properties: isPresent(ann.properties) ? MapWrapper.createFromStringMap(ann.properties) : null,
|
||||
setters: setters,
|
||||
readAttributes: readAttributes
|
||||
});
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {Injector, Key, Dependency, bind, Binding, ResolvedBinding, NoBindingError,
|
||||
AbstractBindingError, CyclicDependencyError} from 'angular2/di';
|
||||
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
|
||||
import {PropertySetter, Attribute, Query} from 'angular2/src/core/annotations/di';
|
||||
import {Attribute, Query} from 'angular2/src/core/annotations/di';
|
||||
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';
|
||||
@ -195,15 +195,13 @@ export class TreeNode {
|
||||
|
||||
export class DirectiveDependency extends Dependency {
|
||||
depth:int;
|
||||
propSetterName:string;
|
||||
attributeName:string;
|
||||
queryDirective;
|
||||
|
||||
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, properties:List,
|
||||
depth:int, propSetterName: string, attributeName:string, queryDirective) {
|
||||
depth:int, attributeName:string, queryDirective) {
|
||||
super(key, asPromise, lazy, optional, properties);
|
||||
this.depth = depth;
|
||||
this.propSetterName = propSetterName;
|
||||
this.attributeName = attributeName;
|
||||
this.queryDirective = queryDirective;
|
||||
this._verify();
|
||||
@ -211,17 +209,15 @@ export class DirectiveDependency extends Dependency {
|
||||
|
||||
_verify():void {
|
||||
var count = 0;
|
||||
if (isPresent(this.propSetterName)) count++;
|
||||
if (isPresent(this.queryDirective)) count++;
|
||||
if (isPresent(this.attributeName)) count++;
|
||||
if (count > 1) throw new BaseException(
|
||||
'A directive injectable can contain only one of the following @PropertySetter, @Attribute or @Query.');
|
||||
'A directive injectable can contain only one of the following @Attribute or @Query.');
|
||||
}
|
||||
|
||||
static createFrom(d:Dependency):Dependency {
|
||||
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional,
|
||||
d.properties, DirectiveDependency._depth(d.properties),
|
||||
DirectiveDependency._propSetterName(d.properties),
|
||||
DirectiveDependency._attributeName(d.properties),
|
||||
DirectiveDependency._query(d.properties)
|
||||
);
|
||||
@ -234,11 +230,6 @@ export class DirectiveDependency extends Dependency {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static _propSetterName(properties):string {
|
||||
var p = ListWrapper.find(properties, (p) => p instanceof PropertySetter);
|
||||
return isPresent(p) ? p.propName : null;
|
||||
}
|
||||
|
||||
static _attributeName(properties):string {
|
||||
var p = ListWrapper.find(properties, (p) => p instanceof Attribute);
|
||||
return isPresent(p) ? p.attributeName : null;
|
||||
@ -268,6 +259,10 @@ export class DirectiveBinding extends ResolvedBinding {
|
||||
}
|
||||
}
|
||||
|
||||
get displayName() {
|
||||
return this.key.displayName;
|
||||
}
|
||||
|
||||
get eventEmitters():List<string> {
|
||||
return isPresent(this.annotation) && isPresent(this.annotation.events) ? this.annotation.events : [];
|
||||
}
|
||||
@ -735,7 +730,6 @@ export class ElementInjector extends TreeNode {
|
||||
}
|
||||
|
||||
_getByDependency(dep:DirectiveDependency, requestor:Key) {
|
||||
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
|
||||
if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
|
||||
if (isPresent(dep.queryDirective)) return this._findQuery(dep.queryDirective).list;
|
||||
if (dep.key.id === StaticKeys.instance().elementRefId) {
|
||||
@ -744,15 +738,6 @@ export class ElementInjector extends TreeNode {
|
||||
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
|
||||
}
|
||||
|
||||
_buildPropSetter(dep) {
|
||||
var view = this._getPreBuiltObjectByKeyId(StaticKeys.instance().viewId);
|
||||
var renderer = view.renderer;
|
||||
var index = this._proto.index;
|
||||
return function(v) {
|
||||
renderer.setElementProperty(view.render, index, dep.propSetterName, v);
|
||||
};
|
||||
}
|
||||
|
||||
_buildAttribute(dep): string {
|
||||
var attributes = this._proto.attributes;
|
||||
if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) {
|
||||
|
@ -3,7 +3,7 @@ import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
|
||||
import {isPresent, isBlank} from 'angular2/src/facade/lang';
|
||||
import {reflector} from 'angular2/src/reflection/reflection';
|
||||
|
||||
import {ChangeDetection} from 'angular2/change_detection';
|
||||
import {ChangeDetection, DirectiveIndex} from 'angular2/change_detection';
|
||||
import {Component, Viewport, DynamicComponent} from '../annotations/annotations';
|
||||
|
||||
import * as renderApi from 'angular2/src/render/api';
|
||||
@ -18,7 +18,8 @@ export class ProtoViewFactory {
|
||||
this._changeDetection = changeDetection;
|
||||
}
|
||||
|
||||
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto, directives:List<DirectiveBinding>):AppProtoView {
|
||||
createProtoView(componentBinding:DirectiveBinding, renderProtoView: renderApi.ProtoViewDto,
|
||||
directives:List<DirectiveBinding>):AppProtoView {
|
||||
var protoChangeDetector;
|
||||
if (isBlank(componentBinding)) {
|
||||
protoChangeDetector = this._changeDetection.createProtoChangeDetector('root', null);
|
||||
@ -43,7 +44,7 @@ export class ProtoViewFactory {
|
||||
this._createElementBinder(
|
||||
protoView, renderElementBinder, protoElementInjector, sortedDirectives
|
||||
);
|
||||
this._createDirectiveBinders(protoView, sortedDirectives);
|
||||
this._createDirectiveBinders(protoView, i, sortedDirectives);
|
||||
}
|
||||
MapWrapper.forEach(renderProtoView.variableBindings, (mappedName, varName) => {
|
||||
protoView.bindVariable(varName, mappedName);
|
||||
@ -128,27 +129,34 @@ export class ProtoViewFactory {
|
||||
return elBinder;
|
||||
}
|
||||
|
||||
_createDirectiveBinders(protoView, sortedDirectives) {
|
||||
for (var i=0; i<sortedDirectives.renderDirectives.length; i++) {
|
||||
var renderDirectiveMetadata = sortedDirectives.renderDirectives[i];
|
||||
_createDirectiveBinders(protoView, boundElementIndex, sortedDirectives) {
|
||||
for (var i = 0; i < sortedDirectives.renderDirectives.length; i++) {
|
||||
var directiveBinder = sortedDirectives.renderDirectives[i];
|
||||
|
||||
// directive properties
|
||||
MapWrapper.forEach(renderDirectiveMetadata.propertyBindings, (astWithSource, propertyName) => {
|
||||
MapWrapper.forEach(directiveBinder.propertyBindings, (astWithSource, propertyName) => {
|
||||
// TODO: these setters should eventually be created by change detection, to make
|
||||
// it monomorphic!
|
||||
var setter = reflector.setter(propertyName);
|
||||
protoView.bindDirectiveProperty(i, astWithSource, propertyName, setter);
|
||||
});
|
||||
|
||||
// host properties
|
||||
MapWrapper.forEach(directiveBinder.hostPropertyBindings, (astWithSource, propertyName) => {
|
||||
var directiveIndex = new DirectiveIndex(boundElementIndex, i);
|
||||
protoView.bindHostElementProperty(astWithSource, propertyName, directiveIndex);
|
||||
});
|
||||
|
||||
// directive events
|
||||
protoView.bindEvent(renderDirectiveMetadata.eventBindings, i);
|
||||
protoView.bindEvent(directiveBinder.eventBindings, i);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SortedDirectives {
|
||||
componentDirective: DirectiveBinding;
|
||||
viewportDirective: DirectiveBinding;
|
||||
renderDirectives: List<renderApi.DirectiveMetadata>;
|
||||
renderDirectives: List<renderApi.DirectiveBinder>;
|
||||
directives: List<DirectiveBinding>;
|
||||
|
||||
constructor(renderDirectives, allDirectives) {
|
||||
@ -156,18 +164,18 @@ class SortedDirectives {
|
||||
this.directives = [];
|
||||
this.viewportDirective = null;
|
||||
this.componentDirective = null;
|
||||
ListWrapper.forEach(renderDirectives, (renderDirectiveMetadata) => {
|
||||
var directiveBinding = allDirectives[renderDirectiveMetadata.directiveIndex];
|
||||
ListWrapper.forEach(renderDirectives, (renderDirectiveBinder) => {
|
||||
var directiveBinding = allDirectives[renderDirectiveBinder.directiveIndex];
|
||||
if ((directiveBinding.annotation instanceof Component) || (directiveBinding.annotation instanceof DynamicComponent)) {
|
||||
// component directives need to be the first binding in ElementInjectors!
|
||||
this.componentDirective = directiveBinding;
|
||||
ListWrapper.insert(this.renderDirectives, 0, renderDirectiveMetadata);
|
||||
ListWrapper.insert(this.renderDirectives, 0, renderDirectiveBinder);
|
||||
ListWrapper.insert(this.directives, 0, directiveBinding);
|
||||
} else {
|
||||
if (directiveBinding.annotation instanceof Viewport) {
|
||||
this.viewportDirective = directiveBinding;
|
||||
}
|
||||
ListWrapper.push(this.renderDirectives, renderDirectiveMetadata);
|
||||
ListWrapper.push(this.renderDirectives, renderDirectiveBinder);
|
||||
ListWrapper.push(this.directives, directiveBinding);
|
||||
}
|
||||
});
|
||||
|
16
modules/angular2/src/core/compiler/view.js
vendored
16
modules/angular2/src/core/compiler/view.js
vendored
@ -1,6 +1,6 @@
|
||||
import {ListWrapper, MapWrapper, Map, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {AST, Locals, ChangeDispatcher, ProtoChangeDetector, ChangeDetector,
|
||||
ChangeRecord, BindingRecord, DirectiveRecord, ChangeDetectorRef} from 'angular2/change_detection';
|
||||
ChangeRecord, BindingRecord, DirectiveRecord, DirectiveIndex, ChangeDetectorRef} from 'angular2/change_detection';
|
||||
|
||||
import {ProtoElementInjector, ElementInjector, PreBuiltObjects, DirectiveBinding} from './element_injector';
|
||||
import {ElementBinder} from './element_binder';
|
||||
@ -124,12 +124,12 @@ export class AppView {
|
||||
}
|
||||
}
|
||||
|
||||
getDirectiveFor(directive:DirectiveRecord) {
|
||||
getDirectiveFor(directive:DirectiveIndex) {
|
||||
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||
return elementInjector.getDirectiveAtIndex(directive.directiveIndex);
|
||||
}
|
||||
|
||||
getDetectorFor(directive:DirectiveRecord) {
|
||||
getDetectorFor(directive:DirectiveIndex) {
|
||||
var elementInjector = this.elementInjectors[directive.elementIndex];
|
||||
return elementInjector.getChangeDetector();
|
||||
}
|
||||
@ -267,6 +267,14 @@ export class AppProtoView {
|
||||
ListWrapper.push(this.bindings, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an host property binding for the last created ElementBinder via bindElement
|
||||
*/
|
||||
bindHostElementProperty(expression:AST, setterName:string, directiveIndex:DirectiveIndex):void {
|
||||
var b = BindingRecord.createForHostProperty(directiveIndex, expression, setterName);
|
||||
ListWrapper.push(this.bindings, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event binding for the last created ElementBinder via bindElement.
|
||||
*
|
||||
@ -323,7 +331,7 @@ export class AppProtoView {
|
||||
var changeDetection = binding.changeDetection;
|
||||
|
||||
MapWrapper.set(this._directiveRecordsMap, id,
|
||||
new DirectiveRecord(elementInjectorIndex, directiveIndex,
|
||||
new DirectiveRecord(new DirectiveIndex(elementInjectorIndex, directiveIndex),
|
||||
binding.callOnAllChangesDone, binding.callOnChange, changeDetection));
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user