Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending component decorator with base class decorator

I have several component decorator declarations that I repeat on every component, for example:

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

How can I apply these declarations to all my components? I tried to create a base class with this decorator and extend other classes with it but base class decorations doesn't seem to apply to derivative classes.

like image 499
dstr Avatar asked Apr 25 '16 10:04

dstr


3 Answers

@Component is a decorator. This means that it handles the class it applies on by adding some metadata data leveraging the reflect-metadata library. Angular2 doesn't look for metadata on parent classes. For this reason, it's not possible to use decorators on parent classes.

Regarding the BootstrapInputDirective directive, you could define it as a platform one. This way you wouldn't need to include it each time into the directives attribute of your components.

Here is a sample:

(...)
import {PLATFORM_DIRECTIVES} from 'angular2/core';

bootstrap(AppComponent, [
  provide(PLATFORM_DIRECTIVES, {useValue: [BootstrapInputDirective], multi:true})
]);

Edit

Yes, you could create your own decorator to implement this. Here is a sample:

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 this way:

@Component({
  template: `
    <div>Test</div>
  `
})
export class AbstractComponent {
}

@CustomComponent({
  selector: 'sub',
  parent: AbstractComponent
})
export class SubComponent extends AbstractComponent {
}

Note that we need to provide the parent class as input of the decorator since we can find out this parent class within the decorator. Only the prototype of this class but the metadata are applied on the class and not on the associated prototype by reflect-metadata.

Edit2

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

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 reference the parent class in the custom decorator.

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

See this question:

  • How to get the parent class at runtime
like image 191
Thierry Templier Avatar answered Nov 12 '22 02:11

Thierry Templier


Just in case you are looking for isPresent function:

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

like image 9
dipcore Avatar answered Nov 12 '22 02:11

dipcore


After latest releases of Angular, the ComponentMetadata class isn't available as pointed out by few members here.

This is how I've implemented the 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 it helps!

EDIT: the previous chunk of code, even if it works, it overrides the original metadata of the extended class. Find below an enhanced version of it, allowing you to have multiple inheritances and overrides without modifying 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];
    });
};

}

like image 8
Guinnberg Avatar answered Nov 12 '22 02:11

Guinnberg