如何扩展/继承组件?

Fer*_*eal 138 inheritance typescript angular-directive angular

我想为已经部署在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);
  }
}
Run Code Online (Sandbox Code Playgroud)

您是否只想更改另一个衍生组件,例如,在示例颜色的情况下,基本组件行为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";
  }
}
Run Code Online (Sandbox Code Playgroud)

在Plunker中完成工作示例

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

正如您在衍生组件的实现中所看到的app/my-panel.component.ts,大部分实现都是重复的,单个部分真正继承了class BasePanelComponent,但@Component必须基本上完全重复,而不仅仅是更改的部分,如selector: 'my-panel'.

有没有办法对组件Angular2进行字面上的完全继承,继承class标记/注释的定义,例如@Component

编辑1 - 功能请求

功能请求angular2添加到GitHub上的项目:Extend/Inherit angular2 components annotations#7968

编辑2 - 关闭请求

由于这个原因,请求被关闭,暂时不知道如何合并装饰器.离开我们没有选择.所以我的意见是在问题中引用的.

Fer*_*eal 34

替代方案:

Thierry Templier的答案是解决问题的另一种方法.

在与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);
  }
}
Run Code Online (Sandbox Code Playgroud)

2 - 带@Component装饰器的基础组件:

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

}
Run Code Online (Sandbox Code Playgroud)

3 - 带@CustomComponent装饰器的子组件:

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

Plunkr有完整的例子.

  • 我假设这与离线模板编译器不兼容. (3认同)

Dan*_*com 25

Angular 2版本2.3刚刚发布,它包含本机组件继承.看起来你可以继承和覆盖你想要的任何东西,模板和样式除外.一些参考:


Mas*_*mar 22

让我们了解 Angular 的组件继承系统的一些关键限制和特性。

组件只继承类逻辑:

  • @Component 装饰器中的所有元数据都不会被继承。
  • 组件@Input 属性和@Output 属性是继承的。
  • 组件生命周期不是继承的。

记住这些功能非常重要,因此让我们独立检查每个功能。

Component 只继承类逻辑

当你继承一个 Component 时,里面的所有逻辑都是同等继承的。值得注意的是,只有公共成员被继承,因为私有成员只能在实现它们的类中访问。

@Component 装饰器中的所有元数据都不会被继承

没有元数据被继承的事实乍一看似乎有悖常理,但是,如果你考虑一下,它实际上是完全有道理的。如果您从 Component 继承,例如 (componentA),您将不希望您继承的 ComponentA 的选择器替换 ComponentB 的选择器,而 ComponentB 是继承的类。template/templateUrl 和 style/styleUrls 也是如此。

组件@Input 和@Output 属性被继承

这是我非常喜欢 Angular 中的组件继承的另一个特性。简而言之,只要您有自定义的 @Input 和 @Output 属性,这些属性就会被继承。

组件生命周期不继承

这部分不是那么明显,特别是对于没有广泛使用 OOP 原则的人。例如,假设您有 ComponentA,它实现了 Angular 的许多生命周期钩子之一,如 OnInit。如果您创建 ComponentB 并继承 ComponentA,则来自 ComponentA 的 OnInit 生命周期不会触发,直到您显式调用它,即使您确实具有 ComponentB 的此 OnInit 生命周期。

调用超级/基本组件方法

为了让 ComponentA 的 ngOnInit() 方法被触发,我们需要使用 super 关键字,然后调用我们需要的方法,在这种情况下是 ngOnInit。super 关键字指的是正在继承的组件的实例,在这种情况下将是 ComponentA。


Joh*_*lph 17

现在TypeScript 2.2 通过Class表达式支持Mixins,我们有更好的方法来表达Mixins on Components.请注意,您也可以使用组件继承,因为角度2.3(讨论)或自定义装饰器,如此处的其他答案中所讨论的.但是,我认为Mixins有一些属性使它们更适合重用组件之间的行为:

  • Mixins组成更灵活,即您可以在现有组件上混合和匹配Mixins,或组合Mixins以形成新组件
  • 由于其对类继承层次结构的明显线性化,因此Mixin组合仍然易于理解
  • 您可以更轻松地避免装饰器和注释组件继承的注释问题(讨论)

我强烈建议您阅读上面的TypeScript 2.2声明,以了解Mixins的工作原理.角度GitHub问题中的链接讨论提供了更多细节.

你需要这些类型:

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

export class MixinRoot {
}
Run Code Online (Sandbox Code Playgroud)

然后你可以像这个Destroyablemixin 一样声明一个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
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

要混合Destroyable成a Component,你可以像这样声明你的组件:

export class DashboardComponent extends Destroyable(MixinRoot) 
    implements OnInit, OnDestroy { ... }
Run Code Online (Sandbox Code Playgroud)

请注意,MixinRoot仅在您想要extendMixin合成时才需要.您可以轻松扩展多个mixins,例如A extends B(C(D)).这是我上面讨论的mixins的明显线性化,例如你正在有效地构成一个继承层次结构A -> B -> C -> D.

在其他情况下,例如,当您想要在现有类上组合Mixins时,您可以像这样应用Mixin:

const MyClassWithMixin = MyMixin(MyClass);
Run Code Online (Sandbox Code Playgroud)

但是,我发现第一种方式最适合ComponentsDirectives,因为这些也需要装饰@Component或者@Directive无论如何.

  • 我最终使用`函数Destroyable <T extends Constructor <{} >>(Base = class {}作为T)`.这样我就可以使用`extends Destroyable()`创建mixins. (2认同)

am0*_*0wa 15

更新

2.3.0-rc.0起支持组件继承

原版的

到目前为止,最方便的对我来说是保持模板和样式到单独*html*.css文件,并通过指定的templateUrlstyleUrls,所以很容易重复使用.

@Component {
    selector: 'my-panel',
    templateUrl: 'app/components/panel.html', 
    styleUrls: ['app/components/panel.css']
}
export class MyPanelComponent extends BasePanelComponent
Run Code Online (Sandbox Code Playgroud)

  • 这正是我需要的.@Paonent装饰器对于BasePanelComponent会是什么样的?它可以引用不同的html/css文件吗?它可以引用MyPanelComponent引用的相同html/css文件吗? (2认同)

wat*_*zon 10

据我所知,组件继承尚未在Angular 2中实现,我不确定他们是否有计划,但是由于Angular 2使用的是typescript(如果你决定走这条路线),你可以使用类继承通过做class MyClass extends OtherClass { ... }.对于组件继承,我建议通过访问https://github.com/angular/angular/issues并提交功能请求来参与Angular 2项目!


rob*_*ing 5

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

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

如果抽象类包含共享变量或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();
    });
  }
}
Run Code Online (Sandbox Code Playgroud)

这是基础,因为在整个大型代码库中,infiniteSubscriptions.push()仅使用了10次。导入和扩展AutoUnsubscribe实际上比在组件本身的ngOnDestroy()方法中添加mySubscription.unsubscribe()实际需要更多的代码,但这仍然需要其他逻辑。