When and how s decorator applied to the decorated

2020-02-13 03:51发布

问题:

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.

回答1:

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;