feat(core): add support for @Property and @Event decorators
Example: @Directive({selector: 'my-selector'}) class MyDirective { @Property() prop; @Property('el-prop') prop2; @Event() event; @Event('el-event') event2; } Closes #3992
This commit is contained in:
parent
337ce21149
commit
896add7d77
|
@ -34,7 +34,13 @@ export {
|
|||
QueryFactory,
|
||||
ViewQuery,
|
||||
Pipe,
|
||||
PipeFactory
|
||||
PipeFactory,
|
||||
Property,
|
||||
PropertyFactory,
|
||||
PropertyMetadata,
|
||||
Event,
|
||||
EventFactory,
|
||||
EventMetadata
|
||||
} from './src/core/metadata';
|
||||
|
||||
export {
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
import {resolveForwardRef, Injectable} from 'angular2/di';
|
||||
import {Type, isPresent, BaseException, stringify} from 'angular2/src/core/facade/lang';
|
||||
import {DirectiveMetadata} from 'angular2/metadata';
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {
|
||||
DirectiveMetadata,
|
||||
ComponentMetadata,
|
||||
PropertyMetadata,
|
||||
EventMetadata
|
||||
} from 'angular2/metadata';
|
||||
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||
|
||||
/**
|
||||
|
@ -16,15 +22,78 @@ export class DirectiveResolver {
|
|||
* Return {@link DirectiveMetadata} for a given `Type`.
|
||||
*/
|
||||
resolve(type: Type): DirectiveMetadata {
|
||||
var annotations = reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(annotations)) {
|
||||
for (var i = 0; i < annotations.length; i++) {
|
||||
var annotation = annotations[i];
|
||||
if (annotation instanceof DirectiveMetadata) {
|
||||
return annotation;
|
||||
var typeMetadata = reflector.annotations(resolveForwardRef(type));
|
||||
if (isPresent(typeMetadata)) {
|
||||
for (var i = 0; i < typeMetadata.length; i++) {
|
||||
var metadata = typeMetadata[i];
|
||||
if (metadata instanceof DirectiveMetadata) {
|
||||
var propertyMetadata = reflector.propMetadata(type);
|
||||
return this._mergeWithPropertyMetadata(metadata, propertyMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
|
||||
}
|
||||
|
||||
private _mergeWithPropertyMetadata(dm: DirectiveMetadata,
|
||||
propertyMetadata:
|
||||
StringMap<string, any[]>): DirectiveMetadata {
|
||||
var properties = [];
|
||||
var events = [];
|
||||
|
||||
StringMapWrapper.forEach(propertyMetadata, (metadata: any[], propName: string) => {
|
||||
metadata.forEach(a => {
|
||||
if (a instanceof PropertyMetadata) {
|
||||
if (isPresent(a.bindingPropertyName)) {
|
||||
properties.push(`${propName}: ${a.bindingPropertyName}`);
|
||||
} else {
|
||||
properties.push(propName);
|
||||
}
|
||||
}
|
||||
|
||||
if (a instanceof EventMetadata) {
|
||||
if (isPresent(a.bindingPropertyName)) {
|
||||
events.push(`${propName}: ${a.bindingPropertyName}`);
|
||||
} else {
|
||||
events.push(propName);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return this._merge(dm, properties, events);
|
||||
}
|
||||
|
||||
private _merge(dm: DirectiveMetadata, properties: string[], events: string[]): DirectiveMetadata {
|
||||
var mergedProperties =
|
||||
isPresent(dm.properties) ? ListWrapper.concat(dm.properties, properties) : properties;
|
||||
var mergedEvents = isPresent(dm.events) ? ListWrapper.concat(dm.events, events) : events;
|
||||
|
||||
if (dm instanceof ComponentMetadata) {
|
||||
return new ComponentMetadata({
|
||||
selector: dm.selector,
|
||||
properties: mergedProperties,
|
||||
events: mergedEvents,
|
||||
host: dm.host,
|
||||
lifecycle: dm.lifecycle,
|
||||
bindings: dm.bindings,
|
||||
exportAs: dm.exportAs,
|
||||
compileChildren: dm.compileChildren,
|
||||
changeDetection: dm.changeDetection,
|
||||
viewBindings: dm.viewBindings
|
||||
});
|
||||
|
||||
} else {
|
||||
return new DirectiveMetadata({
|
||||
selector: dm.selector,
|
||||
properties: mergedProperties,
|
||||
events: mergedEvents,
|
||||
host: dm.host,
|
||||
lifecycle: dm.lifecycle,
|
||||
bindings: dm.bindings,
|
||||
exportAs: dm.exportAs,
|
||||
compileChildren: dm.compileChildren
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,3 +96,19 @@ class ViewQuery extends ViewQueryMetadata {
|
|||
const ViewQuery(dynamic /*Type | string*/ selector, {bool descendants: false})
|
||||
: super(selector, descendants: descendants);
|
||||
}
|
||||
|
||||
/**
|
||||
* See: [PropertyMetadata] for docs.
|
||||
*/
|
||||
class Property extends PropertyMetadata {
|
||||
const Property([String bindingPropertyName])
|
||||
: super(bindingPropertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* See: [EventMetadata] for docs.
|
||||
*/
|
||||
class Event extends EventMetadata {
|
||||
const Event([String bindingPropertyName])
|
||||
: super(bindingPropertyName);
|
||||
}
|
|
@ -13,7 +13,9 @@ export {
|
|||
ComponentMetadata,
|
||||
DirectiveMetadata,
|
||||
PipeMetadata,
|
||||
LifecycleEvent
|
||||
LifecycleEvent,
|
||||
PropertyMetadata,
|
||||
EventMetadata
|
||||
} from './metadata/directives';
|
||||
|
||||
export {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
||||
|
@ -29,13 +31,21 @@ import {
|
|||
ComponentMetadata,
|
||||
DirectiveMetadata,
|
||||
PipeMetadata,
|
||||
LifecycleEvent
|
||||
LifecycleEvent,
|
||||
PropertyMetadata,
|
||||
EventMetadata
|
||||
} from './metadata/directives';
|
||||
|
||||
import {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
||||
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
|
||||
|
||||
import {makeDecorator, makeParamDecorator, TypeDecorator, Class} from './util/decorators';
|
||||
import {
|
||||
makeDecorator,
|
||||
makeParamDecorator,
|
||||
makePropDecorator,
|
||||
TypeDecorator,
|
||||
Class
|
||||
} from './util/decorators';
|
||||
import {Type} from 'angular2/src/core/facade/lang';
|
||||
|
||||
/**
|
||||
|
@ -397,6 +407,46 @@ export interface PipeFactory {
|
|||
}): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link PropertyMetadata} factory for creating decorators.
|
||||
*
|
||||
* ## Example as TypeScript Decorator
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* selector: 'sample-dir'
|
||||
* })
|
||||
* class SampleDir {
|
||||
* @Property() property; // Same as @Property('property') property;
|
||||
* @Property("el-property") dirProperty;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface PropertyFactory {
|
||||
(bindingPropertyName?: string): any;
|
||||
new (bindingPropertyName?: string): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EventMetadata} factory for creating decorators.
|
||||
*
|
||||
* ## Example as TypeScript Decorator
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* selector: 'sample-dir'
|
||||
* })
|
||||
* class SampleDir {
|
||||
* @Event() event = new EventEmitter(); // Same as @Event('event') event = new EventEmitter();
|
||||
* @Event("el-event") dirEvent = new EventEmitter();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export interface EventFactory {
|
||||
(bindingPropertyName?: string): any;
|
||||
new (bindingPropertyName?: string): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ComponentMetadata} factory function.
|
||||
*/
|
||||
|
@ -433,3 +483,13 @@ export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryMetadata);
|
|||
* {@link PipeMetadata} factory function.
|
||||
*/
|
||||
export var Pipe: PipeFactory = <PipeFactory>makeDecorator(PipeMetadata);
|
||||
|
||||
/**
|
||||
* {@link PropertyMetadata} factory function.
|
||||
*/
|
||||
export var Property: PropertyFactory = makePropDecorator(PropertyMetadata);
|
||||
|
||||
/**
|
||||
* {@link EventMetadata} factory function.
|
||||
*/
|
||||
export var Event: EventFactory = makePropDecorator(EventMetadata);
|
|
@ -1070,3 +1070,43 @@ export class PipeMetadata extends InjectableMetadata {
|
|||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a bound field.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* selector: 'sample-dir'
|
||||
* })
|
||||
* class SampleDir {
|
||||
* @Property() property; // Same as @Property('property') property;
|
||||
* @Property("el-property") dirProperty;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class PropertyMetadata {
|
||||
constructor(public bindingPropertyName?: string) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a bound event.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```
|
||||
* @Directive({
|
||||
* selector: 'sample-dir'
|
||||
* })
|
||||
* class SampleDir {
|
||||
* @Event() event = new EventEmitter(); // Same as @Event('event') event = new EventEmitter();
|
||||
* @Event("el-event") dirEvent = new EventEmitter();
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@CONST()
|
||||
export class EventMetadata {
|
||||
constructor(public bindingPropertyName?: string) {}
|
||||
}
|
|
@ -41,6 +41,11 @@ class ReflectionCapabilities extends standard.ReflectionCapabilities {
|
|||
return super.annotations(typeOrFunc);
|
||||
}
|
||||
|
||||
Map propMetadata(typeOrFunc) {
|
||||
_notify('propMetadata', typeOrFunc);
|
||||
return super.propMetadata(typeOrFunc);
|
||||
}
|
||||
|
||||
GetterFn getter(String name) {
|
||||
_notify('getter', name);
|
||||
return super.getter(name);
|
||||
|
|
|
@ -5,8 +5,9 @@ export interface PlatformReflectionCapabilities {
|
|||
isReflectionEnabled(): boolean;
|
||||
factory(type: Type): Function;
|
||||
interfaces(type: Type): any[];
|
||||
parameters(type: Type): any[][];
|
||||
annotations(type: Type): any[];
|
||||
parameters(type: any): any[][];
|
||||
annotations(type: any): any[];
|
||||
propMetadata(typeOrFunc: any): StringMap<string, any[]>;
|
||||
getter(name: string): GetterFn;
|
||||
setter(name: string): SetterFn;
|
||||
method(name: string): MethodFn;
|
||||
|
|
|
@ -27,6 +27,10 @@ class NoReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
Map propMetadata(Type type) {
|
||||
throw "Cannot find reflection information on ${stringify(type)}";
|
||||
}
|
||||
|
||||
GetterFn getter(String name) {
|
||||
throw "Cannot find getter ${name}";
|
||||
}
|
||||
|
|
|
@ -255,6 +255,19 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||
return meta.map((m) => m.reflectee).toList();
|
||||
}
|
||||
|
||||
Map propMetadata(typeOrFunc) {
|
||||
final res = {};
|
||||
reflectClass(typeOrFunc).declarations.forEach((k,v) {
|
||||
var name = _normalizeName(MirrorSystem.getName(k));
|
||||
res[name] = v.metadata.map((fm) => fm.reflectee).toList();
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
String _normalizeName(String name) {
|
||||
return name.endsWith("=") ? name.substring(0, name.length - 1) : name;
|
||||
}
|
||||
|
||||
List interfaces(type) {
|
||||
ClassMirror classMirror = reflectType(type);
|
||||
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();
|
||||
|
|
|
@ -109,37 +109,53 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||
return result;
|
||||
}
|
||||
|
||||
parameters(typeOfFunc: Type): any[][] {
|
||||
parameters(typeOrFunc: Type): any[][] {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOfFunc).parameters)) {
|
||||
return (<any>typeOfFunc).parameters;
|
||||
if (isPresent((<any>typeOrFunc).parameters)) {
|
||||
return (<any>typeOrFunc).parameters;
|
||||
}
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
|
||||
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
|
||||
var paramAnnotations = this._reflect.getMetadata('parameters', typeOrFunc);
|
||||
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOrFunc);
|
||||
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||
return this._zipTypesAndAnnotaions(paramTypes, paramAnnotations);
|
||||
}
|
||||
}
|
||||
return ListWrapper.createFixedSize((<any>typeOfFunc).length);
|
||||
return ListWrapper.createFixedSize((<any>typeOrFunc).length);
|
||||
}
|
||||
|
||||
annotations(typeOfFunc: Type): any[] {
|
||||
annotations(typeOrFunc: Type): any[] {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOfFunc).annotations)) {
|
||||
var annotations = (<any>typeOfFunc).annotations;
|
||||
if (isPresent((<any>typeOrFunc).annotations)) {
|
||||
var annotations = (<any>typeOrFunc).annotations;
|
||||
if (isFunction(annotations) && annotations.annotations) {
|
||||
annotations = annotations.annotations;
|
||||
}
|
||||
return annotations;
|
||||
}
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var annotations = this._reflect.getMetadata('annotations', typeOfFunc);
|
||||
var annotations = this._reflect.getMetadata('annotations', typeOrFunc);
|
||||
if (isPresent(annotations)) return annotations;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
propMetadata(typeOrFunc: any): StringMap<string, any[]> {
|
||||
// Prefer the direct API.
|
||||
if (isPresent((<any>typeOrFunc).propMetadata)) {
|
||||
var propMetadata = (<any>typeOrFunc).propMetadata;
|
||||
if (isFunction(propMetadata) && propMetadata.propMetadata) {
|
||||
propMetadata = propMetadata.propMetadata;
|
||||
}
|
||||
return propMetadata;
|
||||
}
|
||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||
var propMetadata = this._reflect.getMetadata('propMetadata', typeOrFunc);
|
||||
if (isPresent(propMetadata)) return propMetadata;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
interfaces(type: Type): any[] {
|
||||
throw new BaseException("JavaScript does not support interfaces");
|
||||
}
|
||||
|
|
|
@ -14,17 +14,8 @@ export {SetterFn, GetterFn, MethodFn} from './types';
|
|||
export {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||
|
||||
export class ReflectionInfo {
|
||||
_factory: Function;
|
||||
_annotations: any[];
|
||||
_parameters: any[][];
|
||||
_interfaces: any[];
|
||||
|
||||
constructor(annotations?: any[], parameters?: any[][], factory?: Function, interfaces?: any[]) {
|
||||
this._annotations = annotations;
|
||||
this._parameters = parameters;
|
||||
this._factory = factory;
|
||||
this._interfaces = interfaces;
|
||||
}
|
||||
constructor(public annotations?: any[], public parameters?: any[][], public factory?: Function,
|
||||
public interfaces?: any[], public propMetadata?: StringMap<string, any[]>) {}
|
||||
}
|
||||
|
||||
export class Reflector {
|
||||
|
@ -87,7 +78,7 @@ export class Reflector {
|
|||
|
||||
factory(type: Type): Function {
|
||||
if (this._containsReflectionInfo(type)) {
|
||||
var res = this._getReflectionInfo(type)._factory;
|
||||
var res = this._getReflectionInfo(type).factory;
|
||||
return isPresent(res) ? res : null;
|
||||
} else {
|
||||
return this.reflectionCapabilities.factory(type);
|
||||
|
@ -96,7 +87,7 @@ export class Reflector {
|
|||
|
||||
parameters(typeOrFunc: /*Type*/ any): any[] {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc)._parameters;
|
||||
var res = this._getReflectionInfo(typeOrFunc).parameters;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.parameters(typeOrFunc);
|
||||
|
@ -105,16 +96,25 @@ export class Reflector {
|
|||
|
||||
annotations(typeOrFunc: /*Type*/ any): any[] {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc)._annotations;
|
||||
var res = this._getReflectionInfo(typeOrFunc).annotations;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.annotations(typeOrFunc);
|
||||
}
|
||||
}
|
||||
|
||||
propMetadata(typeOrFunc: /*Type*/ any): StringMap<string, any[]> {
|
||||
if (this._injectableInfo.has(typeOrFunc)) {
|
||||
var res = this._getReflectionInfo(typeOrFunc).propMetadata;
|
||||
return isPresent(res) ? res : {};
|
||||
} else {
|
||||
return this.reflectionCapabilities.propMetadata(typeOrFunc);
|
||||
}
|
||||
}
|
||||
|
||||
interfaces(type: Type): any[] {
|
||||
if (this._injectableInfo.has(type)) {
|
||||
var res = this._getReflectionInfo(type)._interfaces;
|
||||
var res = this._getReflectionInfo(type).interfaces;
|
||||
return isPresent(res) ? res : [];
|
||||
} else {
|
||||
return this.reflectionCapabilities.interfaces(type);
|
||||
|
|
|
@ -290,3 +290,24 @@ export function makeParamDecorator(annotationCls): any {
|
|||
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
|
||||
return ParamDecoratorFactory;
|
||||
}
|
||||
|
||||
export function makePropDecorator(decoratorCls): any {
|
||||
function PropDecoratorFactory(...args): any {
|
||||
var decoratorInstance = Object.create(decoratorCls.prototype);
|
||||
decoratorCls.apply(decoratorInstance, args);
|
||||
|
||||
if (this instanceof decoratorCls) {
|
||||
return decoratorInstance;
|
||||
} else {
|
||||
return function PropDecorator(target: any, name: string) {
|
||||
var meta = Reflect.getOwnMetadata('propMetadata', target.constructor);
|
||||
meta = meta || {};
|
||||
meta[name] = meta[name] || [];
|
||||
meta[name].unshift(decoratorInstance);
|
||||
Reflect.defineMetadata('propMetadata', meta, target.constructor);
|
||||
};
|
||||
}
|
||||
}
|
||||
PropDecoratorFactory.prototype = Object.create(decoratorCls.prototype);
|
||||
return PropDecoratorFactory;
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
|
||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||
import {DirectiveMetadata, Directive} from 'angular2/metadata';
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirective {
|
||||
}
|
||||
|
||||
@Directive({selector: 'someChildDirective'})
|
||||
class SomeChildDirective extends SomeDirective {
|
||||
}
|
||||
|
||||
class SomeDirectiveWithoutAnnotation {}
|
||||
|
||||
export function main() {
|
||||
describe("DirectiveResolver", () => {
|
||||
var reader;
|
||||
|
||||
beforeEach(() => { reader = new DirectiveResolver(); });
|
||||
|
||||
it('should read out the Directive annotation', () => {
|
||||
var directiveMetadata = reader.resolve(SomeDirective);
|
||||
expect(directiveMetadata).toEqual(new Directive({selector: 'someDirective'}));
|
||||
});
|
||||
|
||||
it('should throw if not matching annotation is found', () => {
|
||||
expect(() => { reader.resolve(SomeDirectiveWithoutAnnotation); })
|
||||
.toThrowError('No Directive annotation found on SomeDirectiveWithoutAnnotation');
|
||||
});
|
||||
|
||||
it('should not read parent class Directive annotations', function() {
|
||||
var directiveMetadata = reader.resolve(SomeChildDirective);
|
||||
expect(directiveMetadata).toEqual(new Directive({selector: 'someChildDirective'}));
|
||||
});
|
||||
});
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import {ddescribe, describe, it, iit, expect, beforeEach} from 'angular2/test_lib';
|
||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||
import {DirectiveMetadata, Directive, Property, Event} from 'angular2/metadata';
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirective {
|
||||
}
|
||||
|
||||
@Directive({selector: 'someChildDirective'})
|
||||
class SomeChildDirective extends SomeDirective {
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective', properties: ['c']})
|
||||
class SomeDirectiveWithProps {
|
||||
@Property() a;
|
||||
@Property("renamed") b;
|
||||
c;
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective', events: ['c']})
|
||||
class SomeDirectiveWithEvents {
|
||||
@Event() a;
|
||||
@Event("renamed") b;
|
||||
c;
|
||||
}
|
||||
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithSetterProps {
|
||||
@Property("renamed")
|
||||
set a(value) {
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: 'someDirective'})
|
||||
class SomeDirectiveWithGetterEvents {
|
||||
@Event("renamed")
|
||||
get a() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class SomeDirectiveWithoutMetadata {}
|
||||
|
||||
export function main() {
|
||||
describe("DirectiveResolver", () => {
|
||||
var resolver;
|
||||
|
||||
beforeEach(() => { resolver = new DirectiveResolver(); });
|
||||
|
||||
it('should read out the Directive metadata', () => {
|
||||
var directiveMetadata = resolver.resolve(SomeDirective);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(new DirectiveMetadata({selector: 'someDirective', properties: [], events: []}));
|
||||
});
|
||||
|
||||
it('should throw if not matching metadata is found', () => {
|
||||
expect(() => { resolver.resolve(SomeDirectiveWithoutMetadata); })
|
||||
.toThrowError('No Directive annotation found on SomeDirectiveWithoutMetadata');
|
||||
});
|
||||
|
||||
it('should not read parent class Directive metadata', function() {
|
||||
var directiveMetadata = resolver.resolve(SomeChildDirective);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(
|
||||
new DirectiveMetadata({selector: 'someChildDirective', properties: [], events: []}));
|
||||
});
|
||||
|
||||
describe('properties', () => {
|
||||
it('should append directive properties', () => {
|
||||
var directiveMetadata = resolver.resolve(SomeDirectiveWithProps);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(new DirectiveMetadata(
|
||||
{selector: 'someDirective', properties: ['c', 'a', 'b: renamed'], events: []}));
|
||||
});
|
||||
|
||||
it('should work with getters and setters', () => {
|
||||
var directiveMetadata = resolver.resolve(SomeDirectiveWithSetterProps);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(new DirectiveMetadata(
|
||||
{selector: 'someDirective', properties: ['a: renamed'], events: []}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
it('should append directive events', () => {
|
||||
var directiveMetadata = resolver.resolve(SomeDirectiveWithEvents);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(new DirectiveMetadata(
|
||||
{selector: 'someDirective', properties: [], events: ['c', 'a', 'b: renamed']}));
|
||||
});
|
||||
|
||||
it('should work with getters and setters', () => {
|
||||
var directiveMetadata = resolver.resolve(SomeDirectiveWithGetterEvents);
|
||||
expect(directiveMetadata)
|
||||
.toEqual(new DirectiveMetadata(
|
||||
{selector: 'someDirective', properties: [], events: ['a: renamed']}));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -64,7 +64,17 @@ import {
|
|||
ChangeDetectorGenConfig
|
||||
} from 'angular2/src/core/change_detection/change_detection';
|
||||
|
||||
import {Directive, Component, View, ViewMetadata, Attribute, Query, Pipe} from 'angular2/metadata';
|
||||
import {
|
||||
Directive,
|
||||
Component,
|
||||
View,
|
||||
ViewMetadata,
|
||||
Attribute,
|
||||
Query,
|
||||
Pipe,
|
||||
Property,
|
||||
Event
|
||||
} from 'angular2/metadata';
|
||||
|
||||
import {QueryList} from 'angular2/src/core/compiler/query_list';
|
||||
|
||||
|
@ -1597,6 +1607,48 @@ export function main() {
|
|||
}));
|
||||
}
|
||||
});
|
||||
|
||||
describe('property decorators', () => {
|
||||
it('should support property decorators',
|
||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
|
||||
tcb.overrideView(
|
||||
MyComp, new ViewMetadata({
|
||||
template: '<with-prop-decorators el-prop="aaa"></with-prop-decorators>',
|
||||
directives: [DirectiveWithPropDecorators]
|
||||
}))
|
||||
.createAsync(MyComp)
|
||||
.then((rootTC) => {
|
||||
rootTC.detectChanges();
|
||||
var dir = rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
|
||||
expect(dir.dirProp).toEqual("aaa");
|
||||
async.done();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
if (DOM.supportsDOMEvents()) {
|
||||
it('should support events decorators',
|
||||
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
|
||||
tcb = tcb.overrideView(
|
||||
MyComp, new ViewMetadata({
|
||||
template: `<with-prop-decorators (el-event)="ctxProp='called'">`,
|
||||
directives: [DirectiveWithPropDecorators]
|
||||
}));
|
||||
|
||||
var rootTC;
|
||||
tcb.createAsync(MyComp).then(root => { rootTC = root; });
|
||||
tick();
|
||||
|
||||
var emitter =
|
||||
rootTC.componentViewChildren[0].inject(DirectiveWithPropDecorators);
|
||||
emitter.fireEvent('fired !');
|
||||
|
||||
tick();
|
||||
|
||||
expect(rootTC.componentInstance.ctxProp).toEqual("called");
|
||||
})));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2148,3 +2200,11 @@ class OtherDuplicateDir {
|
|||
class DirectiveThrowingAnError {
|
||||
constructor() { throw new BaseException("BOOM"); }
|
||||
}
|
||||
|
||||
@Directive({selector: 'with-prop-decorators'})
|
||||
class DirectiveWithPropDecorators {
|
||||
@Property("elProp") dirProp: string;
|
||||
@Event('elEvent') event = new EventEmitter();
|
||||
|
||||
fireEvent(msg) { ObservableWrapper.callNext(this.event, msg); }
|
||||
}
|
|
@ -10,6 +10,12 @@ class ParamDecorator {
|
|||
const ParamDecorator(this.value);
|
||||
}
|
||||
|
||||
class PropDecorator {
|
||||
final dynamic value;
|
||||
|
||||
const PropDecorator(this.value);
|
||||
}
|
||||
|
||||
ClassDecorator classDecorator(value) {
|
||||
return new ClassDecorator(value);
|
||||
}
|
||||
|
@ -17,3 +23,7 @@ ClassDecorator classDecorator(value) {
|
|||
ParamDecorator paramDecorator(value) {
|
||||
return new ParamDecorator(value);
|
||||
}
|
||||
|
||||
PropDecorator propDecorator(value) {
|
||||
return new PropDecorator(value);
|
||||
}
|
||||
|
|
|
@ -1,24 +1,33 @@
|
|||
import {makeDecorator, makeParamDecorator} from 'angular2/src/core/util/decorators';
|
||||
import {
|
||||
makeDecorator,
|
||||
makeParamDecorator,
|
||||
makePropDecorator
|
||||
} from 'angular2/src/core/util/decorators';
|
||||
|
||||
export class ClassDecoratorImpl {
|
||||
value;
|
||||
|
||||
constructor(value) { this.value = value; }
|
||||
export class ClassDecoratorMeta {
|
||||
constructor(public value) {}
|
||||
}
|
||||
|
||||
export class ParamDecoratorImpl {
|
||||
value;
|
||||
export class ParamDecoratorMeta {
|
||||
constructor(public value) {}
|
||||
}
|
||||
|
||||
constructor(value) { this.value = value; }
|
||||
export class PropDecoratorMeta {
|
||||
constructor(public value) {}
|
||||
}
|
||||
|
||||
export function classDecorator(value) {
|
||||
return new ClassDecoratorImpl(value);
|
||||
return new ClassDecoratorMeta(value);
|
||||
}
|
||||
|
||||
export function paramDecorator(value) {
|
||||
return new ParamDecoratorImpl(value);
|
||||
return new ParamDecoratorMeta(value);
|
||||
}
|
||||
|
||||
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
|
||||
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
|
||||
export function propDecorator(value) {
|
||||
return new PropDecoratorMeta(value);
|
||||
}
|
||||
|
||||
export var ClassDecorator = makeDecorator(ClassDecoratorMeta);
|
||||
export var ParamDecorator = makeParamDecorator(ParamDecoratorMeta);
|
||||
export var PropDecorator = makePropDecorator(PropDecoratorMeta);
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
import {describe, it, iit, ddescribe, expect, beforeEach} from 'angular2/test_lib';
|
||||
import {Reflector, ReflectionInfo} from 'angular2/src/core/reflection/reflection';
|
||||
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
|
||||
import {ClassDecorator, ParamDecorator, classDecorator, paramDecorator} from './reflector_common';
|
||||
import {
|
||||
ClassDecorator,
|
||||
ParamDecorator,
|
||||
PropDecorator,
|
||||
classDecorator,
|
||||
paramDecorator,
|
||||
propDecorator
|
||||
} from './reflector_common';
|
||||
import {IS_DART} from '../../platform';
|
||||
|
||||
class AType {
|
||||
|
@ -12,9 +19,13 @@ class AType {
|
|||
|
||||
@ClassDecorator('class')
|
||||
class ClassWithDecorators {
|
||||
a;
|
||||
@PropDecorator("p1") @PropDecorator("p2") a;
|
||||
b;
|
||||
|
||||
@PropDecorator("p3")
|
||||
set c(value) {
|
||||
}
|
||||
|
||||
constructor(@ParamDecorator("a") a: AType, @ParamDecorator("b") b: AType) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
|
@ -138,6 +149,19 @@ export function main() {
|
|||
});
|
||||
});
|
||||
|
||||
describe("propMetadata", () => {
|
||||
it("should return a string map of prop metadata for the given class", () => {
|
||||
var p = reflector.propMetadata(ClassWithDecorators);
|
||||
expect(p["a"]).toEqual([propDecorator("p1"), propDecorator("p2")]);
|
||||
expect(p["c"]).toEqual([propDecorator("p3")]);
|
||||
});
|
||||
|
||||
it("should return registered meta if available", () => {
|
||||
reflector.registerType(TestObj, new ReflectionInfo(null, null, null, null, {"a": [1, 2]}));
|
||||
expect(reflector.propMetadata(TestObj)).toEqual({"a": [1, 2]});
|
||||
});
|
||||
});
|
||||
|
||||
describe("annotations", () => {
|
||||
it("should return an array of annotations for a type", () => {
|
||||
var p = reflector.annotations(ClassWithDecorators);
|
||||
|
|
|
@ -20,6 +20,8 @@ class NullReflectionCapabilities implements ReflectionCapabilities {
|
|||
|
||||
List annotations(typeOrFunc) => _notImplemented('annotations');
|
||||
|
||||
Map propMetadata(typeOrFunc) => _notImplemented('propMetadata');
|
||||
|
||||
GetterFn getter(String name) => _nullGetter;
|
||||
|
||||
SetterFn setter(String name) => _nullSetter;
|
||||
|
|
Loading…
Reference in New Issue