Skip to content

Commit b1dc623

Browse files
committed
feat(core): @Attribute annotation
Closes angular#1091 Fixes angular#622
1 parent 3ce0f11 commit b1dc623

File tree

9 files changed

+114
-6
lines changed

9 files changed

+114
-6
lines changed

modules/angular2/src/core/annotations/di.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,15 @@ export class PropertySetter extends DependencyAnnotation {
2626
this.propName = propName;
2727
}
2828
}
29+
30+
/**
31+
* The directive can inject the value of an attribute of the host element
32+
*/
33+
export class Attribute extends DependencyAnnotation {
34+
attributeName: string;
35+
@CONST()
36+
constructor(attributeName) {
37+
super();
38+
this.attributeName = attributeName;
39+
}
40+
}

modules/angular2/src/core/compiler/element_injector.js

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {Math} from 'angular2/src/facade/math';
33
import {List, ListWrapper, MapWrapper} from 'angular2/src/facade/collection';
44
import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError, CyclicDependencyError} from 'angular2/di';
55
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
6-
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
6+
import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di';
77
import * as viewModule from 'angular2/src/core/compiler/view';
88
import {ViewContainer} from 'angular2/src/core/compiler/view_container';
99
import {NgElement} from 'angular2/src/core/dom/element';
@@ -90,19 +90,22 @@ export class DirectiveDependency extends Dependency {
9090
depth:int;
9191
eventEmitterName:string;
9292
propSetterName:string;
93+
attributeName:string;
9394

9495
constructor(key:Key, asPromise:boolean, lazy:boolean, optional:boolean,
95-
properties:List, depth:int, eventEmitterName: string, propSetterName: string) {
96+
properties:List, depth:int, eventEmitterName: string, propSetterName: string, attributeName:string) {
9697
super(key, asPromise, lazy, optional, properties);
9798
this.depth = depth;
9899
this.eventEmitterName = eventEmitterName;
99100
this.propSetterName = propSetterName;
101+
this.attributeName = attributeName;
100102
}
101103

102104
static createFrom(d:Dependency):Dependency {
103105
var depth = 0;
104106
var eventName = null;
105107
var propName = null;
108+
var attributeName = null;
106109
var properties = d.properties;
107110

108111
for (var i = 0; i < properties.length; i++) {
@@ -115,11 +118,13 @@ export class DirectiveDependency extends Dependency {
115118
eventName = property.eventName;
116119
} else if (property instanceof PropertySetter) {
117120
propName = property.propName;
121+
} else if (property instanceof Attribute) {
122+
attributeName = property.attributeName;
118123
}
119124
}
120125

121126
return new DirectiveDependency(d.key, d.asPromise, d.lazy, d.optional, d.properties, depth,
122-
eventName, propName);
127+
eventName, propName, attributeName);
123128
}
124129
}
125130

@@ -209,6 +214,7 @@ export class ProtoElementInjector {
209214
index:int;
210215
view:viewModule.View;
211216
distanceToParent:number;
217+
attributes:Map;
212218

213219
/** Whether the element is exported as $implicit. */
214220
exportElement:boolean;
@@ -514,6 +520,7 @@ export class ElementInjector extends TreeNode {
514520
_getByDependency(dep:DirectiveDependency, requestor:Key) {
515521
if (isPresent(dep.eventEmitterName)) return this._buildEventEmitter(dep);
516522
if (isPresent(dep.propSetterName)) return this._buildPropSetter(dep);
523+
if (isPresent(dep.attributeName)) return this._buildAttribute(dep);
517524
return this._getByKey(dep.key, dep.depth, dep.optional, requestor);
518525
}
519526

@@ -531,6 +538,15 @@ export class ElementInjector extends TreeNode {
531538
return function(v) { setter(domElement, v) };
532539
}
533540

541+
_buildAttribute(dep): string {
542+
var attributes = this._proto.attributes;
543+
if (isPresent(attributes) && MapWrapper.contains(attributes, dep.attributeName)) {
544+
return MapWrapper.get(attributes, dep.attributeName);
545+
} else {
546+
return null;
547+
}
548+
}
549+
534550
/*
535551
* It is fairly easy to annotate keys with metadata.
536552
* For example, key.metadata = 'directive'.

modules/angular2/src/core/compiler/pipeline/compile_element.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class CompileElement {
2222
textNodeBindings:Map;
2323
propertyBindings:Map;
2424
eventBindings:Map;
25+
attributes:Map;
2526

2627
/// Store directive name to template name mapping.
2728
/// Directive name is what the directive exports the variable as
@@ -144,6 +145,13 @@ export class CompileElement {
144145
MapWrapper.set(this.eventBindings, eventName, expression);
145146
}
146147

148+
addAttribute(attributeName:string, attributeValue:string) {
149+
if (isBlank(this.attributes)) {
150+
this.attributes = MapWrapper.create();
151+
}
152+
MapWrapper.set(this.attributes, attributeName, attributeValue);
153+
}
154+
147155
addDirective(directive:DirectiveMetadata) {
148156
var annotation = directive.annotation;
149157
this._allDirectives = null;

modules/angular2/src/core/compiler/pipeline/property_binding_parser.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ export class PropertyBindingParser extends CompileStep {
7272
var ast = this._parseInterpolation(attrValue, desc);
7373
if (isPresent(ast)) {
7474
current.addPropertyBinding(attrName, ast);
75+
} else {
76+
current.addAttribute(attrName, attrValue);
7577
}
7678
}
7779
});

modules/angular2/src/core/compiler/pipeline/proto_element_injector_builder.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export class ProtoElementInjectorBuilder extends CompileStep {
5959
current.inheritedProtoElementInjector.exportImplicitName = exportImplicitName;
6060
}
6161
}
62+
current.inheritedProtoElementInjector.attributes = current.attributes;
6263

6364
} else {
6465
current.inheritedProtoElementInjector = parentProtoElementInjector;

modules/angular2/src/di/binding.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ function _extractToken(typeOrFunc, annotations) {
136136

137137
} else if (paramAnnotation instanceof DependencyAnnotation) {
138138
ListWrapper.push(depProps, paramAnnotation);
139+
} else if (paramAnnotation.name === "string") {
140+
token = paramAnnotation;
139141
}
140142
}
141143

modules/angular2/test/core/compiler/element_injector_spec.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {ListWrapper, MapWrapper, List, StringMapWrapper} from 'angular2/src/faca
44
import {DOM} from 'angular2/src/dom/dom_adapter';
55
import {ProtoElementInjector, PreBuiltObjects, DirectiveBinding} from 'angular2/src/core/compiler/element_injector';
66
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
7-
import {EventEmitter, PropertySetter} from 'angular2/src/core/annotations/di';
7+
import {EventEmitter, PropertySetter, Attribute} from 'angular2/src/core/annotations/di';
88
import {onDestroy} from 'angular2/src/core/annotations/annotations';
99
import {Optional, Injector, Inject, bind} from 'angular2/di';
1010
import {ProtoView, View} from 'angular2/src/core/compiler/view';
@@ -107,6 +107,17 @@ class NeedsPropertySetter {
107107
}
108108
}
109109

110+
class NeedsAttribute {
111+
typeAttribute;
112+
titleAttribute;
113+
fooAttribute;
114+
constructor(@Attribute('type') typeAttribute: string, @Attribute('title') titleAttribute: string, @Attribute('foo') fooAttribute: string) {
115+
this.typeAttribute = typeAttribute;
116+
this.titleAttribute = titleAttribute;
117+
this.fooAttribute = fooAttribute;
118+
}
119+
}
120+
110121
class A_Needs_B {
111122
constructor(dep){}
112123
}
@@ -148,10 +159,11 @@ export function main() {
148159
return [lookupName(tree), children];
149160
}
150161

151-
function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null) {
162+
function injector(bindings, lightDomAppInjector = null, shadowDomAppInjector = null, preBuiltObjects = null, attributes = null) {
152163
if (isBlank(lightDomAppInjector)) lightDomAppInjector = appInjector;
153164

154165
var proto = new ProtoElementInjector(null, 0, bindings, isPresent(shadowDomAppInjector));
166+
proto.attributes = attributes;
155167
var inj = proto.instantiate(null, null);
156168
var preBuilt = isPresent(preBuiltObjects) ? preBuiltObjects : defaultPreBuiltObjects;
157169

@@ -566,5 +578,20 @@ export function main() {
566578
});
567579
});
568580

581+
describe('static', () => {
582+
it('should be injectable', () => {
583+
var attributes = MapWrapper.create();
584+
MapWrapper.set(attributes, 'type', 'text');
585+
MapWrapper.set(attributes, 'title', '');
586+
587+
var inj = injector([NeedsAttribute], null, null, null, attributes);
588+
var needsAttribute = inj.get(NeedsAttribute);
589+
590+
expect(needsAttribute.typeAttribute).toEqual('text');
591+
expect(needsAttribute.titleAttribute).toEqual('');
592+
expect(needsAttribute.fooAttribute).toEqual(null);
593+
});
594+
});
595+
569596
});
570597
}

modules/angular2/test/core/compiler/integration_spec.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {EventManager} from 'angular2/src/core/events/event_manager';
3737
import {Decorator, Component, Viewport, DynamicComponent} from 'angular2/src/core/annotations/annotations';
3838
import {Template} from 'angular2/src/core/annotations/template';
3939
import {Parent, Ancestor} from 'angular2/src/core/annotations/visibility';
40-
import {EventEmitter} from 'angular2/src/core/annotations/di';
40+
import {EventEmitter, Attribute} from 'angular2/src/core/annotations/di';
4141

4242
import {If} from 'angular2/src/directives/if';
4343

@@ -606,6 +606,26 @@ export function main() {
606606
});
607607
});
608608
}));
609+
610+
it('should support static attributes', inject([AsyncTestCompleter], (async) => {
611+
tplResolver.setTemplate(MyComp, new Template({
612+
inline: '<input static type="text" title></input>',
613+
directives: [NeedsAttribute]
614+
}));
615+
compiler.compile(MyComp).then((pv) => {
616+
createView(pv);
617+
618+
var injector = view.elementInjectors[0];
619+
var needsAttribute = injector.get(NeedsAttribute);
620+
expect(needsAttribute.typeAttribute).toEqual('text');
621+
expect(needsAttribute.titleAttribute).toEqual('');
622+
expect(needsAttribute.fooAttribute).toEqual(null);
623+
624+
async.done();
625+
});
626+
}));
627+
628+
609629
});
610630

611631
// Disabled until a solution is found, refs:
@@ -902,3 +922,17 @@ class DecoratorListeningEvent {
902922
class IdComponent {
903923
id: string;
904924
}
925+
926+
@Decorator({
927+
selector: '[static]'
928+
})
929+
class NeedsAttribute {
930+
typeAttribute;
931+
titleAttribute;
932+
fooAttribute;
933+
constructor(@Attribute('type') typeAttribute: string, @Attribute('title') titleAttribute: string, @Attribute('foo') fooAttribute: string) {
934+
this.typeAttribute = typeAttribute;
935+
this.titleAttribute = titleAttribute;
936+
this.fooAttribute = fooAttribute;
937+
}
938+
}

modules/angular2/test/core/compiler/pipeline/property_binding_parser_spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ export function main() {
4646
expect(MapWrapper.get(results[0].propertyBindings, 'a').source).toEqual('{{b}}');
4747
});
4848

49+
it('should detect static attributes', () => {
50+
var results = createPipeline().process(el('<div a="b" c></div>'));
51+
expect(MapWrapper.get(results[0].attributes, 'a')).toEqual('b');
52+
expect(MapWrapper.get(results[0].attributes, 'c')).toEqual('');
53+
});
54+
4955
it('should detect var- syntax', () => {
5056
var results = createPipeline().process(el('<template var-a="b"></template>'));
5157
expect(MapWrapper.get(results[0].variableBindings, 'b')).toEqual('a');

0 commit comments

Comments
 (0)