When and how s decorator applied to the decorated classes from the @angular packages
Asked Answered
B

1

2

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.

Briannebriano answered 24/7, 2017 at 7:27 Comment(0)
B
5

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;
Brinkley answered 24/7, 2017 at 9:8 Comment(8)
thanks for the answer) maybe add some links to the sources? also, it seems that we can rely on decorators being present until the tsickle is supported, correct? I showed the solution here that expects decorators propertyBriannebriano
and I think as soon as Reflect and Decorators are supported natively in a browser, the first two options will not be supportedBriannebriano
Added links. I think we can use Reflect as fallback in this caseBrinkley
@Maximus In case when we don't know in which format library was builded. That's why angular uses all three optionsBrinkley
unfortunately no, Reflect doesn't seem to retrieve metadata from .decorators propertyBriannebriano
No, when we have .decorators we use second options, when metadata was declared by using __decorate([ we should use Reflect.getOwnMetadataBrinkley
can you please show it in this plunker?Briannebriano
plnkr.co/edit/W23YdaaW0hjHob1TN41k?p=preview See also #44882225Brinkley

© 2022 - 2024 — McMap. All rights reserved.