How to extend / inherit components?

I would like to create extensions for some components already deployed in Angular 2, without having to rewrite them almost completely since the base component may undergo changes, and I would like these changes to be reflected in its derived components as well.

I created this simple example to try to better explain my questions:

With the following app/base-panel.component.ts base component:

 import {Component, Input} from 'angular2/core'; @Component({ selector: 'base-panel', template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>', styles: [' .panel{ padding: 50px; } '] }) export class BasePanelComponent { @Input() content: string; color: string = "red"; onClick(event){ console.log("Click color: " + this.color); } } 

Do you want to create another derived component by changing, for example, the basic behavior of the component in the case of the color example, app/my-panel.component.ts :

 import {Component} from 'angular2/core'; import {BasePanelComponent} from './base-panel.component' @Component({ selector: 'my-panel', template: '<div class="panel" [style.background-color]="color" (click)="onClick($event)">{{content}}</div>', styles: [' .panel{ padding: 50px; } '] }) export class MyPanelComponent extends BasePanelComponent{ constructor() { super(); this.color = "blue"; } } 

Full working example in Plunker

Note: obviously, this example is simple and can be solved, otherwise there is no need to use inheritance, but it is intended only to illustrate the real problem.

As you can see in the implementation of the derived component app/my-panel.component.ts , most of the implementation was repeated, and the only part that really was inherited was the BasePanelComponent class , but @Component had to be completely repeated, not just the changed parts as selector: 'my-panel' .

Is there a way to make literally complete inheritance of the Angular2 component by inheriting the definition of the marking / annotation class , such as @Component ?

Edit 1 - Feature Request

Angular2 request added to GitHub project : Extend / Inherit angular2 component annotations # 7968

Edit 2 - Closed request

The request was closed, for this reason , that for a short time it would not be known how to combine the decorator would be done. Leaving us without options. So my opinion is quoted in the issue .

+121
inheritance angular typescript angular-directive angular2-directives
Apr 07 '16 at 12:03
source share
11 answers

Alternative solution:

This answer from Thierry Templier is an alternative way to get around the problem.

After some questions with Thierry Templier, I came up with the following working example, which meets my expectations as an alternative to the inheritance limitation mentioned in this question:

1 - Create your own decorator:

 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); } } 

2 - Base component with the @Component decorator:

 @Component({ // create seletor base for test override property selector: 'master', template: ' <div>Test</div> ' }) export class AbstractComponent { } 

3 - Subcomponent with @CustomComponent decorator:

 @CustomComponent({ // override property annotation //selector: 'sub', selector: (parentSelector) => { return parentSelector + 'sub'} }) export class SubComponent extends AbstractComponent { constructor() { } } 

Plunkr with a complete example.

+28
Jun 28 '16 at 13:10
source share

Angular 2 version 2.3 has just been released, and it includes inheritance of the native component. It looks like you can inherit and redefine everything you want except templates and styles. Some links:

+25
Dec 27 '16 at 2:05
source share

Now that TypeScript 2.2 supports Mixins through class expressions , we have a much better way to express Mixins on Components. Keep in mind that you can also use component inheritance with angular 2.3 ( discussion ) or a custom decorator, as described in other answers here. However, I think Mixins has some properties that make them preferred for reusing behavior between components:

  • Mixers are more difficult to bend, i.e. you can mix and match Mixins on existing components or combine Mixins to create new components
  • Mixin remains easy to understand due to its obvious linearization in the class hierarchy
  • You can easily avoid problems with decorators and annotations that inherit component inheritance ( discussion )

I highly recommend that you read the TypeScript 2.2 announcement above to understand how Mixins works. Related discussions on angular GitHub issues provide additional information.

You will need the following types:

 export type Constructor<T> = new (...args: any[]) => T; export class MixinRoot { } 

And then you can declare Mixin as this Destroyable mixin, which helps components keep track of the subscriptions that need to be removed in ngOnDestroy :

 export function Destroyable<T extends Constructor<{}>>(Base: T) { return class Mixin extends Base implements OnDestroy { private readonly subscriptions: Subscription[] = []; protected registerSubscription(sub: Subscription) { this.subscriptions.push(sub); } public ngOnDestroy() { this.subscriptions.forEach(x => x.unsubscribe()); this.subscriptions.length = 0; // release memory } }; } 

To mixin Destroyable in Component , you declare your component as follows:

 export class DashboardComponent extends Destroyable(MixinRoot) implements OnInit, OnDestroy { ... } 

Please note that MixinRoot is required only if you want to extend the mixin mix. You can easily expand multiple mixes, for example. A extends B(C(D)) . This is the obvious linearization of the mixins that I mentioned above, for example. you effectively make up the inheritnace hierarchy A -> B -> C -> D

In other cases, for example, if you want to create Mixins in an existing class, you can use Mixin like this:

 const MyClassWithMixin = MyMixin(MyClass); 

However, I found that the first method works best for Components and Directives , since they should also be decorated with @Component or @Directive anyway.

+17
May 13 '17 at 14:38
source share

Refresh

Component inheritance supported since 2.3.0-rc.0

original

At the moment, the most convenient for me is to save the template and styles in separate *html & *.css files and specify them using templateUrl and styleUrls so that they can be easily reused.

 @Component { selector: 'my-panel', templateUrl: 'app/components/panel.html', styleUrls: ['app/components/panel.css'] } export class MyPanelComponent extends BasePanelComponent 
+12
Jun 09 '16 at 15:15
source share

As far as I know, component inheritance is not yet implemented in Angular 2, and I'm not sure if they have plans, however, since Angular 2 uses typescript (if you decide to go this route), you can use class inheritance by running class MyClass extends OtherClass { ... } . For component inheritance, I suggest connecting to the Angular 2 project by going to https://github.com/angular/angular/issues and sending a function request!

+10
Apr 7 '16 at 18:43
source share

I know this does not answer your question, but I really believe that component inheritance / extension should be avoided . Here is my reasoning:

If an abstract class extended by two or more components contains common logic: use the service or even create a new typescript class that can be shared between the two components.

If the abstract class ... contains common variables or onClicketc functions, then there will be a duplication between the html of the two kinds of expanding components. This is bad practice and that the general HTML should be broken down into Component (s). These components (parts) can be divided between two components.

Am I missing other reasons for having an abstract class for components?

As an example I saw recently, there were components extending AutoUnsubscribe:

 import { Subscription } from 'rxjs'; import { OnDestroy } from '@angular/core'; export abstract class AutoUnsubscribeComponent implements OnDestroy { protected infiniteSubscriptions: Array<Subscription>; constructor() { this.infiniteSubscriptions = []; } ngOnDestroy() { this.infiniteSubscriptions.forEach((subscription) => { subscription.unsubscribe(); }); } } 

this was the base because in the entire large database, infinSubscriptions.push () was only used 10 times. In addition, importing and extending AutoUnsubscribe actually takes more code than just adding mySubscription.unsubscribe () to the ngOnDestroy () method of the component itself, which in any case required additional logic.

+4
Aug 04 '17 at 0:21
source share

If someone is looking for an updated solution, Fernando'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! } 
+2
Feb 02 '17 at 21:34
source share

Components can be expanded in the same way as typewriting classes inheritance, you just have to redefine the selector with a new name. All Input () and Output () properties from the parent component work as usual.

Refresh

@Component is a decorator,

Decorators are used when declaring a class not on objects.

In essence, decorators add some metadata to the class object and cannot be accessed through inheritance.

If you want to inherit a decorator, I would suggest writing your own decorator. Something like the example below.

 export function CustomComponent(annotation: any) { return function (target: Function) { var parentTarget = Object.getPrototypeOf(target.prototype).constructor; var parentAnnotations = Reflect.getMetadata('annotations', parentTarget); var parentParamTypes = Reflect.getMetadata('design:paramtypes', parentTarget); var parentPropMetadata = Reflect.getMetadata('propMetadata', parentTarget); var parentParameters = Reflect.getMetadata('parameters', parentTarget); var parentAnnotation = parentAnnotations[0]; Object.keys(parentAnnotation).forEach(key => { if (isPresent(parentAnnotation[key])) { if (!isPresent(annotation[key])) { annotation[key] = parentAnnotation[key]; } } }); // Same for the other metadata var metadata = new ComponentMetadata(annotation); Reflect.defineMetadata('annotations', [ metadata ], target); }; }; 

See https://medium.com/@ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7.

+1
May 08 '18 at 6:42
source share
 just use inheritance,Extend parent class in child class and declare constructor with parent class parameter and this parameter use in super(). 1.parent class @Component({ selector: 'teams-players-box', templateUrl: '/maxweb/app/app/teams-players-box.component.html' }) export class TeamsPlayersBoxComponent { public _userProfile:UserProfile; public _user_img:any; public _box_class:string="about-team teams-blockbox"; public fullname:string; public _index:any; public _isView:string; indexnumber:number; constructor( public _userProfilesSvc: UserProfiles, public _router:Router, ){} 2.child class @Component({ selector: '[teams-players-eligibility]', templateUrl: '/maxweb/app/app/teams-players-eligibility.component.html' }) export class TeamsPlayersEligibilityComponent extends TeamsPlayersBoxComponent{ constructor (public _userProfilesSvc: UserProfiles, public _router:Router) { super(_userProfilesSvc,_router); } } 
0
Jun 22 '17 at 11:19 on
source share

Let's look at some of the key limitations and capabilities of the Angulars component inheritance system.

The component inherits only the logic of the class. All metadata in the @Component decorator is not inherited. The properties of the @Input component and the @Output properties are inherited. The component life cycle is not inherited. These functions are very important to consider, so let's look at each of them independently.,

The component inherits only the logic of the class. When you inherit a component, all logic inside is inherited equally. It is worth noting that only public members are inherited, since private members are only available in the class that implements them. All metadata in the @Component decorator is not inherited. The fact that metadata is not inherited at first glance may seem counterintuitive, but if you think about it, it really makes sense. If you are inheriting from the Component component (componentA), you would not want the ComponentA selector you are inheriting from to replace the ComponentB selector, which is the class that is inheriting. The same can be said about template / templateUrl, as well as style / styleUrls.

Component properties @Input and @Output are inherited

This is another feature that I really like about the Inheritance component of Angular. In a simple sentence, when you have custom properties @Input and @Ouput, these properties are inherited.

The component life cycle is not inherited. This part is not too obvious, especially for people who have not worked too much with the principles of OOP. For example, let's say you have a ComponentA that implements one of many Angulars hooks like OnInit. If you create a ComponentB and inherit ComponentA, the OnInit life cycle of ComponentA will not work until you call it explicitly, even if you have this OnInit life cycle for ComponentB.

Calling Super / Base Component Methods To get the ngOnInit () method from the ComponentA fire, we need to use the super keyword and then call the method we need, which in this case is called ngOnInit. The super keyword refers to an instance of the component that is inheriting, from which in this case will be ComponentA.

0
May 13 '19 at 9:04
source share

You can use the <ng-content></ng-content> directive for this.

For example:

Header component

 @Component({ selector: 'header-component', template: ' <header> <ng-content><ng-content> </header> ' }) export class HeaderComponent { } 

Custom header component

 @Component({ selector: 'custom-header-component', template: ' <header-component> This is custom header component <ng-content></ng-content> </header-component> ' }) export class CustomHeaderComponent { } 

<header-component>This is header</header-component> prints This is the header

<custom-header-component>This is header</custom-header-component> prints This is a custom header This is a header

-3
Jan 31 '18 at 10:55
source share



All Articles