diff --git a/modules/angular2/src/core/annotations/di.js b/modules/angular2/src/core/annotations/di.js index c9f54074d2..1f5fec6e12 100644 --- a/modules/angular2/src/core/annotations/di.js +++ b/modules/angular2/src/core/annotations/di.js @@ -26,3 +26,15 @@ export class PropertySetter extends DependencyAnnotation { this.propName = propName; } } + +/** + * The directive can inject the value of an attribute of the host element + */ +export class Attribute extends DependencyAnnotation { + attributeName: string; + @CONST() + constructor(attributeName) { + super(); + this.attributeName = attributeName; + } +} diff --git a/modules/angular2/src/core/compiler/element_injector.js b/modules/angular2/src/core/compiler/element_injector.js index b815d42768..e723fbfc78 100644 --- a/modules/angular2/src/core/compiler/element_injector.js +++ b/modules/angular2/src/core/compiler/element_injector.js @@ -3,7 +3,7 @@ import {Math} from 'angular2/src/facade/math'; import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection'; import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; -import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di'; +import {EventEmitter, PropertySetter, Attribute} 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/dom/element'; @@ -90,19 +90,22 @@ export class DirectiveDependency extends Dependency { depth:int; eventEmitterName:string; propSetterName:string; + attributeName:string; constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean, - properties:List, depth:int, eventEmitterName: string, propSetterName: string) { + properties:List, depth:int, eventEmitterName: string, propSetterName: string, attributeName:string) { super(key, asPromise, lazy, optional, properties); this.depth = depth; this.eventEmitterName = eventEmitterName; this.propSetterName = propSetterName; + this.attributeName = attributeName; } static createFrom(d:Dependency):Dependency { var depth = 0; var eventName = null; var propName = null; + var attributeName = null; var properties = d.properties; for (var i = 0; i < properties.length; i++) { @@ -115,11 +118,13 @@ export class DirectiveDependency extends Dependency { eventName = property.eventName; } else if (property instanceof PropertySetter) { propName = property.propName; + } else if (property instanceof Attribute) { + attributeName = property.attributeName; } } return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, depth, - eventName, propName); + eventName, propName, attributeName); } } @@ -209,6 +214,7 @@ export class ProtoElementInjector { index:int; view:viewModule.View; distanceToParent:number; + attributes:Map; /** Whether the element is exported as $implicit. */ exportElement:boolean; @@ -514,6 +520,7 @@ export class ElementInjector extends TreeNode { _getByDependency(dep:DirectiveDependency, requestor:Key) { if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep); if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep); + if (isPresent(dep.attributeName)) return this._buildAttribute(dep); return this._getByKey(dep.key, dep.depth, dep.optional, requestor); } @@ -531,6 +538,15 @@ export class ElementInjector extends TreeNode { return function(v) { setter(domElement, v) }; } + _buildAttribute(dep): string { + var attributes = this._proto.attributes; + if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) { + return MapWrapper.get(attributes, dep.attributeName); + } else { + return null; + } + } + /* * It is fairly easy to annotate keys with metadata. * For example, key.metadata = 'directive'. diff --git a/modules/angular2/src/core/compiler/pipeline/compile_element.js b/modules/angular2/src/core/compiler/pipeline/compile_element.js index a619b14e3e..22ea0d33a0 100644 --- a/modules/angular2/src/core/compiler/pipeline/compile_element.js +++ b/modules/angular2/src/core/compiler/pipeline/compile_element.js @@ -22,6 +22,7 @@ export class CompileElement { textNodeBindings:Map; propertyBindings:Map; eventBindings:Map; + attributes:Map; /// Store directive name to template name mapping. /// Directive name is what the directive exports the variable as @@ -144,6 +145,13 @@ export class CompileElement { MapWrapper.set(this.eventBindings, eventName, expression); } + addAttribute(attributeName:string, attributeValue:string) { + if (isBlank(this.attributes)) { + this.attributes = MapWrapper.create(); + } + MapWrapper.set(this.attributes, attributeName, attributeValue); + } + addDirective(directive:DirectiveMetadata) { var annotation = directive.annotation; this._allDirectives = null; diff --git a/modules/angular2/src/core/compiler/pipeline/property_binding_parser.js b/modules/angular2/src/core/compiler/pipeline/property_binding_parser.js index b9de3c6413..f2ad4b7d43 100644 --- a/modules/angular2/src/core/compiler/pipeline/property_binding_parser.js +++ b/modules/angular2/src/core/compiler/pipeline/property_binding_parser.js @@ -72,6 +72,8 @@ export class PropertyBindingParser extends CompileStep { var ast = this._parseInterpolation(attrValue, desc); if (isPresent(ast)) { current.addPropertyBinding(attrName, ast); + } else { + current.addAttribute(attrName, attrValue); } } }); diff --git a/modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js b/modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js index b452fe3496..e3f8f09212 100644 --- a/modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js +++ b/modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js @@ -59,6 +59,7 @@ export class ProtoElementInjectorBuilder extends CompileStep { current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName; } } + current.inheritedProtoElementInjector.attributes = current.attributes; } else { current.inheritedProtoElementInjector = parentProtoElementInjector; diff --git a/modules/angular2/src/di/binding.js b/modules/angular2/src/di/binding.js index 51a03a4a5c..9661566526 100644 --- a/modules/angular2/src/di/binding.js +++ b/modules/angular2/src/di/binding.js @@ -136,6 +136,8 @@ function _extractToken(typeOrFunc, annotations) { } else if (paramAnnotation instanceof DependencyAnnotation) { ListWrapper.push(depProps, paramAnnotation); + } else if (paramAnnotation.name === "string") { + token = paramAnnotation; } } diff --git a/modules/angular2/test/core/compiler/element_injector_spec.js b/modules/angular2/test/core/compiler/element_injector_spec.js index 7e4837232c..93f54bc16a 100644 --- a/modules/angular2/test/core/compiler/element_injector_spec.js +++ b/modules/angular2/test/core/compiler/element_injector_spec.js @@ -4,7 +4,7 @@ import {ListWrapper, MapWrapper, List, StringMapWrapper} from 'angular2/src/faca import {DOM} from 'angular2/src/dom/dom_adapter'; import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/src/core/compiler/element_injector'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; -import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di'; +import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di'; import {onDestroy} from 'angular2/src/core/annotations/annotations'; import {Optional, Injector, Inject, bind} from 'angular2/di'; import {ProtoView, View} from 'angular2/src/core/compiler/view'; @@ -107,6 +107,17 @@ class NeedsPropertySetter { } } +class NeedsAttribute { + typeAttribute; + titleAttribute; + fooAttribute; + constructor(@Attribute('type') typeAttribute: string, @Attribute('title') titleAttribute: string, @Attribute('foo') fooAttribute: string) { + this.typeAttribute = typeAttribute; + this.titleAttribute = titleAttribute; + this.fooAttribute = fooAttribute; + } +} + class A_Needs_B { constructor(dep){} } @@ -148,10 +159,11 @@ export function main() { return [lookupName(tree), children]; } - function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null) { + function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null, attributes = null) { if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector; var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector)); + proto.attributes = attributes; var inj = proto.instantiate(null, null); var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects; @@ -566,5 +578,20 @@ export function main() { }); }); + describe('static', () => { + it('should be injectable', () => { + var attributes = MapWrapper.create(); + MapWrapper.set(attributes, 'type', 'text'); + MapWrapper.set(attributes, 'title', ''); + + var inj = injector([NeedsAttribute], null, null, null, attributes); + var needsAttribute = inj.get(NeedsAttribute); + + expect(needsAttribute.typeAttribute).toEqual('text'); + expect(needsAttribute.titleAttribute).toEqual(''); + expect(needsAttribute.fooAttribute).toEqual(null); + }); + }); + }); } diff --git a/modules/angular2/test/core/compiler/integration_spec.js b/modules/angular2/test/core/compiler/integration_spec.js index 384c5acffd..216eb6b798 100644 --- a/modules/angular2/test/core/compiler/integration_spec.js +++ b/modules/angular2/test/core/compiler/integration_spec.js @@ -37,7 +37,7 @@ import {EventManager} from 'angular2/src/core/events/event_manager'; import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations'; import {Template} from 'angular2/src/core/annotations/template'; import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility'; -import {EventEmitter} from 'angular2/src/core/annotations/di'; +import {EventEmitter, Attribute} from 'angular2/src/core/annotations/di'; import {If} from 'angular2/src/directives/if'; @@ -606,6 +606,26 @@ export function main() { }); }); })); + + it('should support static attributes', inject([AsyncTestCompleter], (async) => { + tplResolver.setTemplate(MyComp, new Template({ + inline: '', + directives: [NeedsAttribute] + })); + compiler.compile(MyComp).then((pv) => { + createView(pv); + + var injector = view.elementInjectors[0]; + var needsAttribute = injector.get(NeedsAttribute); + expect(needsAttribute.typeAttribute).toEqual('text'); + expect(needsAttribute.titleAttribute).toEqual(''); + expect(needsAttribute.fooAttribute).toEqual(null); + + async.done(); + }); + })); + + }); // Disabled until a solution is found, refs: @@ -902,3 +922,17 @@ class DecoratorListeningEvent { class IdComponent { id: string; } + +@Decorator({ + selector: '[static]' +}) +class NeedsAttribute { + typeAttribute; + titleAttribute; + fooAttribute; + constructor(@Attribute('type') typeAttribute: string, @Attribute('title') titleAttribute: string, @Attribute('foo') fooAttribute: string) { + this.typeAttribute = typeAttribute; + this.titleAttribute = titleAttribute; + this.fooAttribute = fooAttribute; + } +} diff --git a/modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js b/modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js index 0baea9d41c..69ee4764d1 100644 --- a/modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js +++ b/modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js @@ -46,6 +46,12 @@ export function main() { expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}'); }); + it('should detect static attributes', () => { + var results = createPipeline().process(el('
')); + expect(MapWrapper.get(results[0].attributes, 'a')).toEqual('b'); + expect(MapWrapper.get(results[0].attributes, 'c')).toEqual(''); + }); + it('should detect var- syntax', () => { var results = createPipeline().process(el('')); expect(MapWrapper.get(results[0].variableBindings, 'b')).toEqual('a');