How to extend/inherit components?

2019-01-12 17:40发布

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

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

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

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

Would you like to create another derivative component only alter, for example, a basic component behavior in the case of the example color, 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";
  }
}

Complete working example in Plunker

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

As you can see in the implementation of the derivative component app/my-panel.component.ts, much of the implementation was repeated, and the single part really inherited was the class BasePanelComponent, but the @Component had to basically be completely repeated, not just the changed portions, as the selector: 'my-panel'.

Is there some way to make a literally full inheritance of a component Angular2, inheriting the class definition of the markings/annotations, as for example @Component?

Edit 1 - Feature Request

Feature request angular2 added to the project on GitHub: Extend/Inherit angular2 components annotations #7968

Edit 2 - Closed Request

The request was closed, for this reason, that briefly would not know how to merge the decorator will be made. Leaving us with no options. So my opinion is is quoted in the Issue.

10条回答
淡お忘
2楼-- · 2019-01-12 17:42

As far as I know component inheritance has not been implemented yet in Angular 2 and I'm not sure if they have plans to, however since Angular 2 is using typescript (if you've decided to go that route) you can use class inheritance by doing class MyClass extends OtherClass { ... }. For component inheritance I'd suggest getting involved with the Angular 2 project by going to https://github.com/angular/angular/issues and submitting a feature request!

查看更多
兄弟一词,经得起流年.
3楼-- · 2019-01-12 17:45

Now that TypeScript 2.2 supports Mixins through Class expressions we have a much better way to express Mixins on Components. Mind you that you can also use Component inheritance since angular 2.3 (discussion) or a custom decorator as discussed in other answers here. However, I think Mixins have some properties that make them preferable for reusing behavior across components:

  • Mixins compose more flexibly, i.e. you can mix and match Mixins on existing components or combine Mixins to form new Components
  • Mixin composition remains easy to understand thanks to its obvious linearization to a class inheritance hierarchy
  • You can more easily avoid issues with decorators and annotations that plague component inheritance (discussion)

I strongly suggest you read the TypeScript 2.2 announcement above to understand how Mixins work. The linked discussions in angular GitHub issues provide additional detail.

You'll need these types:

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

export class MixinRoot {
}

And then you can declare a Mixin like this Destroyable mixin that helps components keep track of subscriptions that need to be disposed 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 into a Component, you declare your component like this:

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

Note that MixinRoot is only necessary when you want to extend a Mixin composition. You can easily extend multiple mixins e.g. A extends B(C(D)). This is the obvious linearization of mixins I was talking about above, e.g. you're effectively composing an inheritnace hierarchy A -> B -> C -> D.

In other cases, e.g. when you want to compose Mixins on an existing class, you can apply the Mixin like so:

const MyClassWithMixin = MyMixin(MyClass);

However, I found the first way works best for Components and Directives, as these also need to be decorated with @Component or @Directive anyway.

查看更多
孤傲高冷的网名
4楼-- · 2019-01-12 17:47

Alternative Solution:

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

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

1 - Create custom 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 @Component decorator:

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

}

3 - Sub component with @CustomComponent decorator:

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

Plunkr with complete example.

查看更多
淡お忘
5楼-- · 2019-01-12 17:49
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);
        }
}
查看更多
够拽才男人
6楼-- · 2019-01-12 17:59

Angular 2 version 2.3 was just released, and it includes native component inheritance. It looks like you can inherit and override whatever you want, except for templates and styles. Some references:

查看更多
相关推荐>>
7楼-- · 2019-01-12 18:00

I know this doesn't answer your question but I really think inheriting / extending components should be avoided. Here's my reasoning:

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

If the abstract class... contains shared variables or onClicketc functions, Then there will be duplication between the html of the two extending components views. This is bad practice & that shared html needs to be broken into Component(s). These Component(s) (parts) can be shared between the two components.

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

An example I saw recently was 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 bas because throughout a large codebase, infiniteSubscriptions.push() was only used 10 times. Also importing & extending AutoUnsubscribe actually takes more code than just adding mySubscription.unsubscribe() in the ngOnDestroy() method of the component itself, which required additional logic anyway.

查看更多
登录 后发表回答