Component expander with base class decorator

I have several component decorator descriptors that I repeat for each component, for example:

@Component({ moduleId: module.id, directives: [BootstrapInputDirective] }) 

How can I apply these ads to all my components? I tried to create a base class with this decorator and extend its classes, but the base class decorations don't seem to be related to derived classes.

+12
angular typescript
Apr 25 '16 at 10:01
source share
5 answers

@Component is a decorator. This means that it processes the class to which it is applied by adding some metadata data using the reflection metadata library. Angular2 does not look up metadata of parent classes. For this reason, decorators cannot be used in parent classes.

As for the BootstrapInputDirective directive, you can define it as a platform. This way, you will not need to include it every time in the directives attribute of your components.

Here is an example:

 (...) import {PLATFORM_DIRECTIVES} from 'angular2/core'; bootstrap(AppComponent, [ provide(PLATFORM_DIRECTIVES, {useValue: [BootstrapInputDirective], multi:true}) ]); 

Edit

Yes, you can create your own decorator to implement this. Here is an example:

 export function CustomComponent(annotation: any) { return function (target: Function) { var parentTarget = annotation.parent; delete annotation.parent; var parentAnnotations = Reflect.getMetadata('annotations', parentTarget); var parentAnnotation = parentAnnotations[0]; Object.keys(parentAnnotation).forEach(key => { if (isPresent(parentAnnotation[key])) { annotation[key] = parentAnnotation[key]; } }); var metadata = new ComponentMetadata(annotation); Reflect.defineMetadata('annotations', [ metadata ], target); } } 

The CustomComponent decorator will be used as follows:

 @Component({ template: ` <div>Test</div> ` }) export class AbstractComponent { } @CustomComponent({ selector: 'sub', parent: AbstractComponent }) export class SubComponent extends AbstractComponent { } 

Please note that we need to provide the parent class as a decorator contribution, since we can recognize this parent class in the decorator. Only the prototype of this class, but the metadata is applied to the class, and not to the associated prototype using reflection metadata.

Edit2

Thanks to Nitzam's answer, here is an improvement:

 export function CustomComponent(annotation: any) { return function (target: Function) { var parentTarget = Object.getPrototypeOf(target.prototype).constructor; var parentAnnotations = Reflect.getMetadata('annotations', parentTarget); var parentAnnotation = parentAnnotations[0]; Object.keys(parentAnnotation).forEach(key => { if (isPresent(parentAnnotation[key])) { annotation[key] = parentAnnotation[key]; } }); var metadata = new ComponentMetadata(annotation); Reflect.defineMetadata('annotations', [ metadata ], target); } } 

There is no need for a parent attribute to refer to a parent class in a custom decorator.

See this plunkr: https://plnkr.co/edit/ks1iK41sIBFlYDb4aTHG?p=preview .

See this question:

  • How to get the parent class at runtime
+20
Apr 25 '16 at 10:04 on
source share

Since recent releases of Angular, the ComponentMetadata class is not available, as indicated by several members here.

Here's how I applied CustomComponent to make it work:

 export function CustomComponent(annotation: any) { return function (target: Function) { let parentTarget = Object.getPrototypeOf(target.prototype).constructor; let parentAnnotations = Reflect.getOwnMetadata('annotations', parentTarget); let parentAnnotation = parentAnnotations[0]; Object.keys(annotation).forEach(key => { parentAnnotation[key] = annotation[key]; }); }; } 

Hope this helps!

EDIT : the previous code snippet, even if it works, it overrides the original metadata of the extended class. Find the extended version below that allows you to have multiple inheritances and overrides without changing the base class.

 export function ExtendComponent(annotation: any) { return function (target: Function) { let currentTarget = target.prototype.constructor; let parentTarget = Object.getPrototypeOf(target.prototype).constructor; let parentAnnotations = Reflect.getOwnMetadata('annotations', parentTarget); Reflect.defineMetadata('annotations', [Object.create(parentAnnotations[0])], currentTarget); let currentAnnotations = Reflect.getOwnMetadata('annotations', currentTarget); Object.keys(annotation).forEach(key => { currentAnnotations[0][key] = annotation[key]; }); }; 

}

+7
Dec 29 '16 at 12:37
source share

If someone is looking for an updated solution, Thierry Templier's answer is pretty much perfect. Except that ComponentMetadata deprecated. Using Component instead worked for me.

The complete Custom Decorator CustomDecorator.ts file is as follows:

 import 'zone.js'; import 'reflect-metadata'; import { Component } from '@angular/core'; import { isPresent } from "@angular/platform-browser/src/facade/lang"; export function CustomComponent(annotation: any) { return function (target: Function) { var parentTarget = Object.getPrototypeOf(target.prototype).constructor; var parentAnnotations = Reflect.getMetadata('annotations', parentTarget); var parentAnnotation = parentAnnotations[0]; Object.keys(parentAnnotation).forEach(key => { if (isPresent(parentAnnotation[key])) { // verify is annotation typeof function if(typeof annotation[key] === 'function'){ annotation[key] = annotation[key].call(this, parentAnnotation[key]); }else if( // force override in annotation base !isPresent(annotation[key]) ){ annotation[key] = parentAnnotation[key]; } } }); var metadata = new Component(annotation); Reflect.defineMetadata('annotations', [ metadata ], target); } } 

Then import it into your new sub-component.component.ts and use @CustomComponent instead of @Component as follows:

 import { CustomComponent } from './CustomDecorator'; import { AbstractComponent } from 'path/to/file'; ... @CustomComponent({ selector: 'subcomponent' }) export class SubComponent extends AbstractComponent { constructor() { super(); } // Add new logic here! } 
+5
Feb 02 '17 at 22:08
source share

Just in case, you are looking for the isPresent function:

function isPresent(obj: any): boolean { return obj !== undefined && obj !== null; }

+4
Apr 27 '17 at 18:31 on
source share

You can provide services around the world in your bootstrapping, for example:

 bootstrap(AppComponent, [HTTP_PROVIDERS, provide(SharedService, {useValue: sharedService})]); 

where sharedService is the imported service.

+1
Apr 25 '16 at 10:06
source share