diff --git a/modules/angular2/metadata.ts b/modules/angular2/metadata.ts index 161946e781..472d95f3aa 100644 --- a/modules/angular2/metadata.ts +++ b/modules/angular2/metadata.ts @@ -34,7 +34,13 @@ export { QueryFactory, ViewQuery, Pipe, - PipeFactory + PipeFactory, + Property, + PropertyFactory, + PropertyMetadata, + Event, + EventFactory, + EventMetadata } from './src/core/metadata'; export { diff --git a/modules/angular2/src/core/compiler/directive_resolver.ts b/modules/angular2/src/core/compiler/directive_resolver.ts index 7f9f389f59..00e39c8287 100644 --- a/modules/angular2/src/core/compiler/directive_resolver.ts +++ b/modules/angular2/src/core/compiler/directive_resolver.ts @@ -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): 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 + }); + } + } } diff --git a/modules/angular2/src/core/metadata.dart b/modules/angular2/src/core/metadata.dart index d74caf5140..6e8f248228 100644 --- a/modules/angular2/src/core/metadata.dart +++ b/modules/angular2/src/core/metadata.dart @@ -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); +} \ No newline at end of file diff --git a/modules/angular2/src/core/metadata.ts b/modules/angular2/src/core/metadata.ts index 6f62a15200..0ed9646c71 100644 --- a/modules/angular2/src/core/metadata.ts +++ b/modules/angular2/src/core/metadata.ts @@ -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 = makeDecorator(PipeMetadata); + +/** + * {@link PropertyMetadata} factory function. + */ +export var Property: PropertyFactory = makePropDecorator(PropertyMetadata); + +/** + * {@link EventMetadata} factory function. + */ +export var Event: EventFactory = makePropDecorator(EventMetadata); \ No newline at end of file diff --git a/modules/angular2/src/core/metadata/directives.ts b/modules/angular2/src/core/metadata/directives.ts index db5f312b7b..84fd1e99d5 100644 --- a/modules/angular2/src/core/metadata/directives.ts +++ b/modules/angular2/src/core/metadata/directives.ts @@ -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) {} +} \ No newline at end of file diff --git a/modules/angular2/src/core/reflection/debug_reflection_capabilities.dart b/modules/angular2/src/core/reflection/debug_reflection_capabilities.dart index a24527c1dd..7f36d151bd 100644 --- a/modules/angular2/src/core/reflection/debug_reflection_capabilities.dart +++ b/modules/angular2/src/core/reflection/debug_reflection_capabilities.dart @@ -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); diff --git a/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts b/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts index 93e892157c..36b196d391 100644 --- a/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts +++ b/modules/angular2/src/core/reflection/platform_reflection_capabilities.ts @@ -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; getter(name: string): GetterFn; setter(name: string): SetterFn; method(name: string): MethodFn; diff --git a/modules/angular2/src/core/reflection/reflection.dart b/modules/angular2/src/core/reflection/reflection.dart index 65d8271eb3..6a7d1bd802 100644 --- a/modules/angular2/src/core/reflection/reflection.dart +++ b/modules/angular2/src/core/reflection/reflection.dart @@ -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}"; } diff --git a/modules/angular2/src/core/reflection/reflection_capabilities.dart b/modules/angular2/src/core/reflection/reflection_capabilities.dart index d1e52a7743..1138d7eee1 100644 --- a/modules/angular2/src/core/reflection/reflection_capabilities.dart +++ b/modules/angular2/src/core/reflection/reflection_capabilities.dart @@ -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(); diff --git a/modules/angular2/src/core/reflection/reflection_capabilities.ts b/modules/angular2/src/core/reflection/reflection_capabilities.ts index 6473d790fe..26c0507692 100644 --- a/modules/angular2/src/core/reflection/reflection_capabilities.ts +++ b/modules/angular2/src/core/reflection/reflection_capabilities.ts @@ -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((typeOfFunc).parameters)) { - return (typeOfFunc).parameters; + if (isPresent((typeOrFunc).parameters)) { + return (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((typeOfFunc).length); + return ListWrapper.createFixedSize((typeOrFunc).length); } - annotations(typeOfFunc: Type): any[] { + annotations(typeOrFunc: Type): any[] { // Prefer the direct API. - if (isPresent((typeOfFunc).annotations)) { - var annotations = (typeOfFunc).annotations; + if (isPresent((typeOrFunc).annotations)) { + var annotations = (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 { + // Prefer the direct API. + if (isPresent((typeOrFunc).propMetadata)) { + var propMetadata = (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"); } diff --git a/modules/angular2/src/core/reflection/reflector.ts b/modules/angular2/src/core/reflection/reflector.ts index adae113ab1..8596dd02b0 100644 --- a/modules/angular2/src/core/reflection/reflector.ts +++ b/modules/angular2/src/core/reflection/reflector.ts @@ -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) {} } 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 { + 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); diff --git a/modules/angular2/src/core/util/decorators.ts b/modules/angular2/src/core/util/decorators.ts index 18b115dcba..7a4c7fee9b 100644 --- a/modules/angular2/src/core/util/decorators.ts +++ b/modules/angular2/src/core/util/decorators.ts @@ -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; +} diff --git a/modules/angular2/test/core/compiler/directive_metadata_reader_spec.ts b/modules/angular2/test/core/compiler/directive_metadata_reader_spec.ts deleted file mode 100644 index f3fab6de37..0000000000 --- a/modules/angular2/test/core/compiler/directive_metadata_reader_spec.ts +++ /dev/null @@ -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'})); - }); - }); -} diff --git a/modules/angular2/test/core/compiler/directive_resolver_spec.ts b/modules/angular2/test/core/compiler/directive_resolver_spec.ts new file mode 100644 index 0000000000..d8f9f5a4cc --- /dev/null +++ b/modules/angular2/test/core/compiler/directive_resolver_spec.ts @@ -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']})); + }); + }); + }); +} diff --git a/modules/angular2/test/core/compiler/integration_spec.ts b/modules/angular2/test/core/compiler/integration_spec.ts index 163c694561..f71333cbe8 100644 --- a/modules/angular2/test/core/compiler/integration_spec.ts +++ b/modules/angular2/test/core/compiler/integration_spec.ts @@ -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: '', + 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: ``, + 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); } +} \ No newline at end of file diff --git a/modules/angular2/test/core/reflection/reflector_common.dart b/modules/angular2/test/core/reflection/reflector_common.dart index 8fe8321d5f..f01598e823 100644 --- a/modules/angular2/test/core/reflection/reflector_common.dart +++ b/modules/angular2/test/core/reflection/reflector_common.dart @@ -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); +} diff --git a/modules/angular2/test/core/reflection/reflector_common.ts b/modules/angular2/test/core/reflection/reflector_common.ts index 3481c59423..84223fda9b 100644 --- a/modules/angular2/test/core/reflection/reflector_common.ts +++ b/modules/angular2/test/core/reflection/reflector_common.ts @@ -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); diff --git a/modules/angular2/test/core/reflection/reflector_spec.ts b/modules/angular2/test/core/reflection/reflector_spec.ts index 37b39fc74e..f776c301a0 100644 --- a/modules/angular2/test/core/reflection/reflector_spec.ts +++ b/modules/angular2/test/core/reflection/reflector_spec.ts @@ -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); diff --git a/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart b/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart index aeab6be4b7..76fd1ab53e 100644 --- a/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart +++ b/modules_dart/transform/lib/src/transform/template_compiler/reflection/reflection_capabilities.dart @@ -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;