Access Meta-Annotation inside Class (TypeScript)
Asked Answered
R

4

22

When I annotate a class with metadata in TypeScript, e.g. to create an Angular2 component, can I access the metadata inside that class?

import {Component} from 'angular2/core';

@Component({
    selector: 'app',
    templateUrl: '/app/components/app/app.component.html'
})
export class AppComponent {

    // can I access 'templateUrl' from above annotation here?

}
Raki answered 25/12, 2015 at 18:10 Comment(1)
The general answer is "it depends", a decorator is just something that wraps your class. It is possible for it to expose a proprerty or not - as it chooses. It doesn't have to let you have access to it - in fact it may choose to ignore the parameter passed to it altogether.Interscholastic
K
10

@PierreDuc's answer doesn't work for me in Angular 5. I did some further reading (sorry, I can't find all the relevant links to share), and came up with the following:

let decorator: Type<any>;
// you can cast the component class
decorator = (<any>AppComponent).__annotations__[0];
// or use with lodash.get
decorator = get(AppComponent, '__annotations__[0]');

//
// obviously, this is an array, and based on your app,
// you may have multiple decorators on one class, so iterate them
//


// if you want to just grab some data from the @Component:
const templateUrl: string = decorator.templateUrl;
// or from a @NgModule:
const declarations: Type<any>[] = decorator.declarations;


// or if you're trying to decide whether this is in fact a component:
if (decorator instanceof Component) {
  // do things
}
// or a provider
if (decorator instanceof Injectable) {
  // do different things
}
// or a module:
if (decorator instanceof NgModule) {
  // do things
}

// etc...

I have not yet upgraded my app to Angular 6, so I have not verified that this has not changed.

Related:

Kassiekassity answered 16/5, 2018 at 19:53 Comment(2)
Nice answer! This is indeed how it should be done now, although if you don't care about IE, you can use the native Reflect now. Reflect.getOwnPropertyDescriptor(AppComponent, '__annotations__');Frag
This answer is more current, making it the accepted answer. See #46938246 for a bit more detailsRaki
F
20

With the new angular version, you should use the native Reflect object. No IE11 support though:

const annotations = Reflect.getOwnPropertyDescriptor(MyModule, '__annotations__').value;

Read Jeff Fairley's answer below on what to do with the data after that

You can see an annotation/decorator as a normal function call. To this function the 'Class/Function' object (not instance) gets send in the first parameter, and the parameters (metadata) in the second argument.

However it depends on the implementation of that function if something gets added for instance to the prototype of the class (bad practice/exposing property). The TypeScript compiler and Angular2 do things differently though.

They use the __decorate and __metadata functions, which are generated by the TypeScript compiler. The data gets added with the Object.defineProperty() function. The package responsible for this is Reflect. (which under the hood uses the Object.defineProperty() function in combination with a WeakMap).

The function Reflect.defineMetadata() is used to set the annotations, and to obtain them the obvious Reflect.getMetadata().

TLDR;

  • To get the annotations from a class/component in angular2, you have to use:

    Reflect.getMetadata('annotations', ComponentClass); //@Component({}), @Pipe({}), ...
    
  • To get the annotations from the constructor paramaters in angular2, you have to use:

    Reflect.getMetadata('parameters', ComponentClass); //@Inject()
    
  • To get the annotations from a property in a class in angular2, you have to use:

    Reflect.getMetadata('propMetadata', ComponentClass); //@HostBinding(), @Input(), ...
    

Frag answered 25/12, 2015 at 21:20 Comment(0)
K
10

@PierreDuc's answer doesn't work for me in Angular 5. I did some further reading (sorry, I can't find all the relevant links to share), and came up with the following:

let decorator: Type<any>;
// you can cast the component class
decorator = (<any>AppComponent).__annotations__[0];
// or use with lodash.get
decorator = get(AppComponent, '__annotations__[0]');

//
// obviously, this is an array, and based on your app,
// you may have multiple decorators on one class, so iterate them
//


// if you want to just grab some data from the @Component:
const templateUrl: string = decorator.templateUrl;
// or from a @NgModule:
const declarations: Type<any>[] = decorator.declarations;


// or if you're trying to decide whether this is in fact a component:
if (decorator instanceof Component) {
  // do things
}
// or a provider
if (decorator instanceof Injectable) {
  // do different things
}
// or a module:
if (decorator instanceof NgModule) {
  // do things
}

// etc...

I have not yet upgraded my app to Angular 6, so I have not verified that this has not changed.

Related:

Kassiekassity answered 16/5, 2018 at 19:53 Comment(2)
Nice answer! This is indeed how it should be done now, although if you don't care about IE, you can use the native Reflect now. Reflect.getOwnPropertyDescriptor(AppComponent, '__annotations__');Frag
This answer is more current, making it the accepted answer. See #46938246 for a bit more detailsRaki
M
6

We were using this solution and found out that AOT strips out __annotations__ (which is also the default for --prod builds,) making it a no-go for us.

Fortunately if you're dealing with components, there's an alternative that works in both and doesn't use strings:

const components = [ MyComponent ];
/*
  ...
  Anything that can be injected into
*/
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  // For each component
  components.forEach(element => {
    // Get its resolved factory
    const factory = this.componentFactoryResolver.resolveComponentFactory(element);

    console.log('Factory:', factory);
    console.log('Selector:', factory.selector);
  });
}

Since it's dealing with proper types and interfaces, it's also linter-approved!

Monobasic answered 15/5, 2019 at 22:57 Comment(2)
Should be noted that of the out of all possible annotations only inputs, outputs, queries and selector are accessible from the ComponentFactory.Koala
This doesn't work with directives unfortunately: Type passed in is not ComponentType, it does not have 'ɵcmp' property.Givens
M
0

as John Neuhaus stated, angular just remove annotations property when aot compiling. So, we can just do ng serve --aot=false, and now you may find annotations show up when you do console.dir(AppComponent);

Methodius answered 16/8, 2020 at 16:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.