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,
|
QueryFactory,
|
||||||
ViewQuery,
|
ViewQuery,
|
||||||
Pipe,
|
Pipe,
|
||||||
PipeFactory
|
PipeFactory,
|
||||||
|
Property,
|
||||||
|
PropertyFactory,
|
||||||
|
PropertyMetadata,
|
||||||
|
Event,
|
||||||
|
EventFactory,
|
||||||
|
EventMetadata
|
||||||
} from './src/core/metadata';
|
} from './src/core/metadata';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import {resolveForwardRef, Injectable} from 'angular2/di';
|
import {resolveForwardRef, Injectable} from 'angular2/di';
|
||||||
import {Type, isPresent, BaseException, stringify} from 'angular2/src/core/facade/lang';
|
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';
|
import {reflector} from 'angular2/src/core/reflection/reflection';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -16,15 +22,78 @@ export class DirectiveResolver {
|
|||||||
* Return {@link DirectiveMetadata} for a given `Type`.
|
* Return {@link DirectiveMetadata} for a given `Type`.
|
||||||
*/
|
*/
|
||||||
resolve(type: Type): DirectiveMetadata {
|
resolve(type: Type): DirectiveMetadata {
|
||||||
var annotations = reflector.annotations(resolveForwardRef(type));
|
var typeMetadata = reflector.annotations(resolveForwardRef(type));
|
||||||
if (isPresent(annotations)) {
|
if (isPresent(typeMetadata)) {
|
||||||
for (var i = 0; i < annotations.length; i++) {
|
for (var i = 0; i < typeMetadata.length; i++) {
|
||||||
var annotation = annotations[i];
|
var metadata = typeMetadata[i];
|
||||||
if (annotation instanceof DirectiveMetadata) {
|
if (metadata instanceof DirectiveMetadata) {
|
||||||
return annotation;
|
var propertyMetadata = reflector.propMetadata(type);
|
||||||
|
return this._mergeWithPropertyMetadata(metadata, propertyMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new BaseException(`No Directive annotation found on ${stringify(type)}`);
|
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})
|
const ViewQuery(dynamic /*Type | string*/ selector, {bool descendants: false})
|
||||||
: super(selector, descendants: descendants);
|
: 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,
|
ComponentMetadata,
|
||||||
DirectiveMetadata,
|
DirectiveMetadata,
|
||||||
PipeMetadata,
|
PipeMetadata,
|
||||||
LifecycleEvent
|
LifecycleEvent,
|
||||||
|
PropertyMetadata,
|
||||||
|
EventMetadata
|
||||||
} from './metadata/directives';
|
} from './metadata/directives';
|
||||||
|
|
||||||
export {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
export {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
||||||
@ -29,13 +31,21 @@ import {
|
|||||||
ComponentMetadata,
|
ComponentMetadata,
|
||||||
DirectiveMetadata,
|
DirectiveMetadata,
|
||||||
PipeMetadata,
|
PipeMetadata,
|
||||||
LifecycleEvent
|
LifecycleEvent,
|
||||||
|
PropertyMetadata,
|
||||||
|
EventMetadata
|
||||||
} from './metadata/directives';
|
} from './metadata/directives';
|
||||||
|
|
||||||
import {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
import {ViewMetadata, ViewEncapsulation} from './metadata/view';
|
||||||
import {ChangeDetectionStrategy} from 'angular2/src/core/change_detection/change_detection';
|
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';
|
import {Type} from 'angular2/src/core/facade/lang';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -397,6 +407,46 @@ export interface PipeFactory {
|
|||||||
}): any;
|
}): 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.
|
* {@link ComponentMetadata} factory function.
|
||||||
*/
|
*/
|
||||||
@ -433,3 +483,13 @@ export var ViewQuery: QueryFactory = makeParamDecorator(ViewQueryMetadata);
|
|||||||
* {@link PipeMetadata} factory function.
|
* {@link PipeMetadata} factory function.
|
||||||
*/
|
*/
|
||||||
export var Pipe: PipeFactory = <PipeFactory>makeDecorator(PipeMetadata);
|
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;
|
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);
|
return super.annotations(typeOrFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map propMetadata(typeOrFunc) {
|
||||||
|
_notify('propMetadata', typeOrFunc);
|
||||||
|
return super.propMetadata(typeOrFunc);
|
||||||
|
}
|
||||||
|
|
||||||
GetterFn getter(String name) {
|
GetterFn getter(String name) {
|
||||||
_notify('getter', name);
|
_notify('getter', name);
|
||||||
return super.getter(name);
|
return super.getter(name);
|
||||||
|
@ -5,8 +5,9 @@ export interface PlatformReflectionCapabilities {
|
|||||||
isReflectionEnabled(): boolean;
|
isReflectionEnabled(): boolean;
|
||||||
factory(type: Type): Function;
|
factory(type: Type): Function;
|
||||||
interfaces(type: Type): any[];
|
interfaces(type: Type): any[];
|
||||||
parameters(type: Type): any[][];
|
parameters(type: any): any[][];
|
||||||
annotations(type: Type): any[];
|
annotations(type: any): any[];
|
||||||
|
propMetadata(typeOrFunc: any): StringMap<string, any[]>;
|
||||||
getter(name: string): GetterFn;
|
getter(name: string): GetterFn;
|
||||||
setter(name: string): SetterFn;
|
setter(name: string): SetterFn;
|
||||||
method(name: string): MethodFn;
|
method(name: string): MethodFn;
|
||||||
|
@ -27,6 +27,10 @@ class NoReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||||||
throw "Cannot find reflection information on ${stringify(type)}";
|
throw "Cannot find reflection information on ${stringify(type)}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map propMetadata(Type type) {
|
||||||
|
throw "Cannot find reflection information on ${stringify(type)}";
|
||||||
|
}
|
||||||
|
|
||||||
GetterFn getter(String name) {
|
GetterFn getter(String name) {
|
||||||
throw "Cannot find getter ${name}";
|
throw "Cannot find getter ${name}";
|
||||||
}
|
}
|
||||||
|
@ -255,6 +255,19 @@ class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||||||
return meta.map((m) => m.reflectee).toList();
|
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) {
|
List interfaces(type) {
|
||||||
ClassMirror classMirror = reflectType(type);
|
ClassMirror classMirror = reflectType(type);
|
||||||
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();
|
return classMirror.superinterfaces.map((si) => si.reflectedType).toList();
|
||||||
|
@ -109,37 +109,53 @@ export class ReflectionCapabilities implements PlatformReflectionCapabilities {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters(typeOfFunc: Type): any[][] {
|
parameters(typeOrFunc: Type): any[][] {
|
||||||
// Prefer the direct API.
|
// Prefer the direct API.
|
||||||
if (isPresent((<any>typeOfFunc).parameters)) {
|
if (isPresent((<any>typeOrFunc).parameters)) {
|
||||||
return (<any>typeOfFunc).parameters;
|
return (<any>typeOrFunc).parameters;
|
||||||
}
|
}
|
||||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
||||||
var paramAnnotations = this._reflect.getMetadata('parameters', typeOfFunc);
|
var paramAnnotations = this._reflect.getMetadata('parameters', typeOrFunc);
|
||||||
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOfFunc);
|
var paramTypes = this._reflect.getMetadata('design:paramtypes', typeOrFunc);
|
||||||
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
if (isPresent(paramTypes) || isPresent(paramAnnotations)) {
|
||||||
return this._zipTypesAndAnnotaions(paramTypes, 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.
|
// Prefer the direct API.
|
||||||
if (isPresent((<any>typeOfFunc).annotations)) {
|
if (isPresent((<any>typeOrFunc).annotations)) {
|
||||||
var annotations = (<any>typeOfFunc).annotations;
|
var annotations = (<any>typeOrFunc).annotations;
|
||||||
if (isFunction(annotations) && annotations.annotations) {
|
if (isFunction(annotations) && annotations.annotations) {
|
||||||
annotations = annotations.annotations;
|
annotations = annotations.annotations;
|
||||||
}
|
}
|
||||||
return annotations;
|
return annotations;
|
||||||
}
|
}
|
||||||
if (isPresent(this._reflect) && isPresent(this._reflect.getMetadata)) {
|
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;
|
if (isPresent(annotations)) return annotations;
|
||||||
}
|
}
|
||||||
return [];
|
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[] {
|
interfaces(type: Type): any[] {
|
||||||
throw new BaseException("JavaScript does not support interfaces");
|
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 {PlatformReflectionCapabilities} from './platform_reflection_capabilities';
|
||||||
|
|
||||||
export class ReflectionInfo {
|
export class ReflectionInfo {
|
||||||
_factory: Function;
|
constructor(public annotations?: any[], public parameters?: any[][], public factory?: Function,
|
||||||
_annotations: any[];
|
public interfaces?: any[], public propMetadata?: StringMap<string, 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Reflector {
|
export class Reflector {
|
||||||
@ -87,7 +78,7 @@ export class Reflector {
|
|||||||
|
|
||||||
factory(type: Type): Function {
|
factory(type: Type): Function {
|
||||||
if (this._containsReflectionInfo(type)) {
|
if (this._containsReflectionInfo(type)) {
|
||||||
var res = this._getReflectionInfo(type)._factory;
|
var res = this._getReflectionInfo(type).factory;
|
||||||
return isPresent(res) ? res : null;
|
return isPresent(res) ? res : null;
|
||||||
} else {
|
} else {
|
||||||
return this.reflectionCapabilities.factory(type);
|
return this.reflectionCapabilities.factory(type);
|
||||||
@ -96,7 +87,7 @@ export class Reflector {
|
|||||||
|
|
||||||
parameters(typeOrFunc: /*Type*/ any): any[] {
|
parameters(typeOrFunc: /*Type*/ any): any[] {
|
||||||
if (this._injectableInfo.has(typeOrFunc)) {
|
if (this._injectableInfo.has(typeOrFunc)) {
|
||||||
var res = this._getReflectionInfo(typeOrFunc)._parameters;
|
var res = this._getReflectionInfo(typeOrFunc).parameters;
|
||||||
return isPresent(res) ? res : [];
|
return isPresent(res) ? res : [];
|
||||||
} else {
|
} else {
|
||||||
return this.reflectionCapabilities.parameters(typeOrFunc);
|
return this.reflectionCapabilities.parameters(typeOrFunc);
|
||||||
@ -105,16 +96,25 @@ export class Reflector {
|
|||||||
|
|
||||||
annotations(typeOrFunc: /*Type*/ any): any[] {
|
annotations(typeOrFunc: /*Type*/ any): any[] {
|
||||||
if (this._injectableInfo.has(typeOrFunc)) {
|
if (this._injectableInfo.has(typeOrFunc)) {
|
||||||
var res = this._getReflectionInfo(typeOrFunc)._annotations;
|
var res = this._getReflectionInfo(typeOrFunc).annotations;
|
||||||
return isPresent(res) ? res : [];
|
return isPresent(res) ? res : [];
|
||||||
} else {
|
} else {
|
||||||
return this.reflectionCapabilities.annotations(typeOrFunc);
|
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[] {
|
interfaces(type: Type): any[] {
|
||||||
if (this._injectableInfo.has(type)) {
|
if (this._injectableInfo.has(type)) {
|
||||||
var res = this._getReflectionInfo(type)._interfaces;
|
var res = this._getReflectionInfo(type).interfaces;
|
||||||
return isPresent(res) ? res : [];
|
return isPresent(res) ? res : [];
|
||||||
} else {
|
} else {
|
||||||
return this.reflectionCapabilities.interfaces(type);
|
return this.reflectionCapabilities.interfaces(type);
|
||||||
|
@ -290,3 +290,24 @@ export function makeParamDecorator(annotationCls): any {
|
|||||||
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
|
ParamDecoratorFactory.prototype = Object.create(annotationCls.prototype);
|
||||||
return ParamDecoratorFactory;
|
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'}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
102
modules/angular2/test/core/compiler/directive_resolver_spec.ts
Normal file
102
modules/angular2/test/core/compiler/directive_resolver_spec.ts
Normal file
@ -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
|
ChangeDetectorGenConfig
|
||||||
} from 'angular2/src/core/change_detection/change_detection';
|
} 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';
|
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 {
|
class DirectiveThrowingAnError {
|
||||||
constructor() { throw new BaseException("BOOM"); }
|
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);
|
const ParamDecorator(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PropDecorator {
|
||||||
|
final dynamic value;
|
||||||
|
|
||||||
|
const PropDecorator(this.value);
|
||||||
|
}
|
||||||
|
|
||||||
ClassDecorator classDecorator(value) {
|
ClassDecorator classDecorator(value) {
|
||||||
return new ClassDecorator(value);
|
return new ClassDecorator(value);
|
||||||
}
|
}
|
||||||
@ -17,3 +23,7 @@ ClassDecorator classDecorator(value) {
|
|||||||
ParamDecorator paramDecorator(value) {
|
ParamDecorator paramDecorator(value) {
|
||||||
return new 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 {
|
export class ClassDecoratorMeta {
|
||||||
value;
|
constructor(public value) {}
|
||||||
|
|
||||||
constructor(value) { this.value = value; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ParamDecoratorImpl {
|
export class ParamDecoratorMeta {
|
||||||
value;
|
constructor(public value) {}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(value) { this.value = value; }
|
export class PropDecoratorMeta {
|
||||||
|
constructor(public value) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function classDecorator(value) {
|
export function classDecorator(value) {
|
||||||
return new ClassDecoratorImpl(value);
|
return new ClassDecoratorMeta(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function paramDecorator(value) {
|
export function paramDecorator(value) {
|
||||||
return new ParamDecoratorImpl(value);
|
return new ParamDecoratorMeta(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export var ClassDecorator = makeDecorator(ClassDecoratorImpl);
|
export function propDecorator(value) {
|
||||||
export var ParamDecorator = makeParamDecorator(ParamDecoratorImpl);
|
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 {describe, it, iit, ddescribe, expect, beforeEach} from 'angular2/test_lib';
|
||||||
import {Reflector, ReflectionInfo} from 'angular2/src/core/reflection/reflection';
|
import {Reflector, ReflectionInfo} from 'angular2/src/core/reflection/reflection';
|
||||||
import {ReflectionCapabilities} from 'angular2/src/core/reflection/reflection_capabilities';
|
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';
|
import {IS_DART} from '../../platform';
|
||||||
|
|
||||||
class AType {
|
class AType {
|
||||||
@ -12,9 +19,13 @@ class AType {
|
|||||||
|
|
||||||
@ClassDecorator('class')
|
@ClassDecorator('class')
|
||||||
class ClassWithDecorators {
|
class ClassWithDecorators {
|
||||||
a;
|
@PropDecorator("p1") @PropDecorator("p2") a;
|
||||||
b;
|
b;
|
||||||
|
|
||||||
|
@PropDecorator("p3")
|
||||||
|
set c(value) {
|
||||||
|
}
|
||||||
|
|
||||||
constructor(@ParamDecorator("a") a: AType, @ParamDecorator("b") b: AType) {
|
constructor(@ParamDecorator("a") a: AType, @ParamDecorator("b") b: AType) {
|
||||||
this.a = a;
|
this.a = a;
|
||||||
this.b = b;
|
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", () => {
|
describe("annotations", () => {
|
||||||
it("should return an array of annotations for a type", () => {
|
it("should return an array of annotations for a type", () => {
|
||||||
var p = reflector.annotations(ClassWithDecorators);
|
var p = reflector.annotations(ClassWithDecorators);
|
||||||
|
@ -20,6 +20,8 @@ class NullReflectionCapabilities implements ReflectionCapabilities {
|
|||||||
|
|
||||||
List annotations(typeOrFunc) => _notImplemented('annotations');
|
List annotations(typeOrFunc) => _notImplemented('annotations');
|
||||||
|
|
||||||
|
Map propMetadata(typeOrFunc) => _notImplemented('propMetadata');
|
||||||
|
|
||||||
GetterFn getter(String name) => _nullGetter;
|
GetterFn getter(String name) => _nullGetter;
|
||||||
|
|
||||||
SetterFn setter(String name) => _nullSetter;
|
SetterFn setter(String name) => _nullSetter;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user