我想为一些已经部署在Angular 2中的组件创建扩展,而不需要几乎完全重写它们,因为基本组件可以发生变化,并且希望这些变化也能反映在它的派生组件中。

我创建了这个简单的例子,试图更好地解释我的问题:

使用以下基本组件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);
  }
}

你想创建另一个衍生组件,只改变,例如,一个基本组件的行为,在例子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";
  }
}

完整的工作实例在Plunker

注意:显然这个例子很简单,不需要使用继承就可以解决,但它只是为了说明真正的问题。

正如你在衍生组件app/my-panel.component.ts的实现中看到的,大部分实现是重复的,真正继承的部分是类BasePanelComponent,但是@Component必须基本上完全重复,而不仅仅是改变的部分,作为选择器:'my-panel'。

有没有什么方法可以完全继承angular组件,继承标记/注释的类定义,比如@Component?

编辑1 -功能请求

特性请求angular2添加到GitHub上的项目:扩展/继承angular2组件注释#7968

编辑2 -关闭请求

请求被关闭,因为这个原因,暂时不知道如何合并装饰器将作出。让我们别无选择。所以我的观点在本期杂志上被引用了。


当前回答

如果你通读CDK库和材质库,你会发现它们都在使用继承,但对组件本身使用的不多,在我看来,内容投影才是王道。请看这个链接https://blog.angular-university.io/angular-ng-content/,上面写着“这个设计的关键问题”

我知道这不能回答你的问题,但我真的认为应该避免继承/扩展组件。我的理由是:

如果由两个或多个组件扩展的抽象类包含共享逻辑: 使用一个服务,甚至创建一个新的typescript类,可以在两个组件之间共享。

如果抽象类…包含共享变量或onClicketc函数, 然后,两个扩展组件视图的html之间将出现重复。这是一个不好的做法&共享的html需要被分解成组件。这些组件(部件)可以在两个组件之间共享。

我是否遗漏了为组件创建抽象类的其他原因?

我最近看到的一个例子是组件扩展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();
    });
  }
}

这是因为在整个大型代码库中,infinitessubscriptions .push()只使用了10次。此外,导入和扩展AutoUnsubscribe实际上需要比在组件本身的ngOnDestroy()方法中添加mySubscription.unsubscribe()更多的代码,这需要额外的逻辑。

其他回答

现在TypeScript 2.2通过类表达式支持mixin,我们就有了更好的方法在组件上表达mixin。注意,你也可以从angular 2.3开始使用组件继承(讨论),或者在其他回答中讨论的自定义装饰器。然而,我认为mixin有一些属性,使它们更适合跨组件重用行为:

Mixins的组合更加灵活,也就是说,你可以在现有组件上混合和匹配Mixins,或者组合Mixins来形成新的组件 Mixin组合仍然很容易理解,这要归功于它对类继承层次结构的明显线性化 您可以更容易地避免装饰器和注释的问题,这些问题会困扰组件继承(讨论)

我强烈建议你阅读上面的TypeScript 2.2公告,以了解Mixins是如何工作的。angular GitHub问题中的链接讨论提供了更多细节。

你需要这些类型:

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

export class MixinRoot {
}

然后你可以声明一个类似于Destroyable Mixin的Mixin,它可以帮助组件跟踪需要在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
    }
  };
}

要将可摧毁的组件混合到组件中,你可以这样声明你的组件:

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

注意,只有当您想要扩展Mixin组合时,才需要使用Mixin root。你可以很容易地扩展多个mixin,例如A扩展B(C(D))。这是我上面谈到的mixin的明显线性化,例如,你有效地组成了一个继承层次结构A -> B -> C -> D。

在其他情况下,例如当你想在一个现有的类上组合Mixin时,你可以像这样应用Mixin:

const MyClassWithMixin = MyMixin(MyClass);

然而,我发现第一种方法最适合组件和指令,因为这些也需要用@Component或@Directive来装饰。

Angular 2 2.3版本刚刚发布,它包含了原生组件继承。看起来你可以继承和重写任何你想要的东西,除了模板和样式。参考:

Angular 2.3中的新特性 Angular 2中的组件继承 演示组件继承的Plunkr

可选择的解决方案:

蒂埃里·坦普利埃的回答是解决这个问题的另一种方法。

在与Thierry Templier讨论了一些问题后,我给出了下面的工作示例,它满足了我的期望,可以作为这个问题中提到的继承限制的替代方案:

1 -创建自定义装饰:

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 -使用@Component装饰器的基础组件:

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

}

3 -使用@CustomComponent装饰器的子组件

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

Plunkr的完整示例。

只需使用继承,在子类中扩展父类,并在super()中使用父类形参声明构造函数和此形参。

父类:

@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,
  ){}

子类:

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

如果你通读CDK库和材质库,你会发现它们都在使用继承,但对组件本身使用的不多,在我看来,内容投影才是王道。请看这个链接https://blog.angular-university.io/angular-ng-content/,上面写着“这个设计的关键问题”

我知道这不能回答你的问题,但我真的认为应该避免继承/扩展组件。我的理由是:

如果由两个或多个组件扩展的抽象类包含共享逻辑: 使用一个服务,甚至创建一个新的typescript类,可以在两个组件之间共享。

如果抽象类…包含共享变量或onClicketc函数, 然后,两个扩展组件视图的html之间将出现重复。这是一个不好的做法&共享的html需要被分解成组件。这些组件(部件)可以在两个组件之间共享。

我是否遗漏了为组件创建抽象类的其他原因?

我最近看到的一个例子是组件扩展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();
    });
  }
}

这是因为在整个大型代码库中,infinitessubscriptions .push()只使用了10次。此外,导入和扩展AutoUnsubscribe实际上需要比在组件本身的ngOnDestroy()方法中添加mySubscription.unsubscribe()更多的代码,这需要额外的逻辑。