If I use a decorator in my class the decorator is evaluated when importing the class. Here is the small example:
@NgModule({ ... })
export class BModule { ... }
Transpiled as:
var BModule = (function () {
function BModule() {
}
BModule = __decorate([ <---------- decorators are applied here
core_1.NgModule({...})
], BModule);
return BModule;
}());
exports.BModule = BModule;
However, when the module or any other decorator is applied in the @angular
bundles the output is the following:
var HttpClientModule = (function () {
function HttpClientModule() {
}
return HttpClientModule;
}());
HttpClientModule.decorators = [
{ type: _angular_core.NgModule, args: [{ ... },] },
];
As you can see, the decorators are not applied here. They are just saved in the decorators
property. Why is it different from my code?
The reason I'm asking is that when importing my decorated classes I expect it to have decorators applied and so using Reflect
is possible:
const providers = Reflect.getOwnMetadata('annotations', BModule);
However, it doesn't work this way with decorated classes from the @angular
packages.
When angulat resolves annotations it has three options:
1) Direct API
// Prefer the direct API.
if ((<any>typeOrFunc).annotations && (<any>typeOrFunc).annotations !== parentCtor.annotations) {
let annotations = (<any>typeOrFunc).annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
We usually use this API when write code in ES5
MyComponent.annotations = [
new ng.Component({...})
]
2) API of tsickle
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).decorators && (<any>typeOrFunc).decorators !== parentCtor.decorators) {
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
}
This way angular reads annotations from @angular/(core|material...)
libraries. Angular compiles libraries this way because it helps to optimize bundle. For example we do not need to ship decorator helpers like _decorate, __metadata
and the code will be executed faster.
For that angular uses tslib
when building library by running tsc with --importHelpers
options https://github.com/angular/angular/blob/master/build.sh#L127.
Angular material does the same thing https://github.com/angular/material2/blob/master/tools/package-tools/rollup-helpers.ts#L9-L11
// Import tslib rather than having TypeScript output its helpers multiple times.
// See https://github.com/Microsoft/tslib
'tslib': 'tslib',
3) Using Reflect
// API for metadata created by invoking the decorators.
if (this._reflect && this._reflect.getOwnMetadata) {
return this._reflect.getOwnMetadata('annotations', typeOrFunc);
}
This API is used when we use metadata emitted by typescript
To ensure you will get metadata correctly you can consider using function like:
declare let Reflect: any;
function getAnnotations(typeOrFunc: Type<any>): any[]|null {
// Prefer the direct API.
if ((<any>typeOrFunc).annotations) {
let annotations = (<any>typeOrFunc).annotations;
if (typeof annotations === 'function' && annotations.annotations) {
annotations = annotations.annotations;
}
return annotations;
}
// API of tsickle for lowering decorators to properties on the class.
if ((<any>typeOrFunc).decorators) {
return convertTsickleDecoratorIntoMetadata((<any>typeOrFunc).decorators);
}
// API for metadata created by invoking the decorators.
if (Reflect && Reflect.getOwnMetadata) {
return Reflect.getOwnMetadata('annotations', typeOrFunc);
}
return null;
}
function convertTsickleDecoratorIntoMetadata(decoratorInvocations: any[]): any[] {
if (!decoratorInvocations) {
return [];
}
return decoratorInvocations.map(decoratorInvocation => {
const decoratorType = decoratorInvocation.type;
const annotationCls = decoratorType.annotationCls;
const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
return new annotationCls(...annotationArgs);
});
}
const annotations = getAnnotations(AppModule);
Update:
API for metadata created by invoking the decorators was changed in 5.0.0-beta.4
const ANNOTATIONS = '__annotations__';
// API for metadata created by invoking the decorators.
if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
return (typeOrFunc as any)[ANNOTATIONS];
}
return null;