我想为一些已经部署在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()更多的代码,这需要额外的逻辑。
让我们来了解一下Angular组件继承系统的一些关键限制和特性。
组件只继承类逻辑:
@Component装饰器中的所有元数据都不会被继承。
组件@Input属性和@Output属性被继承。
组件生命周期不继承。
记住这些特性是非常重要的,所以让我们单独研究每一个特性。
组件只继承类逻辑
当你继承一个组件时,里面的所有逻辑都是平等继承的。值得注意的是,只有public成员被继承,而private成员只能在实现它们的类中访问。
@Component装饰器中的所有元数据都不会被继承
没有元数据被继承的事实乍一看似乎是违反直觉的,但如果你仔细想想,它实际上是完全有道理的。如果你从一个组件继承,比如(componentA),你不会想要从componentA继承的选择器替换被继承的类ComponentB的选择器。对于template/templateUrl以及style/styleUrls也是如此。
组件@Input和@Output属性被继承
这是我非常喜欢Angular中组件继承的另一个特性。简单地说,只要有自定义的@Input和@Output属性,这些属性就会被继承。
组件生命周期不继承
这一部分不是很明显,特别是对那些没有广泛使用过面向对象原则的人来说。例如,假设你有ComponentA,它实现了Angular的许多生命周期钩子中的一个,比如OnInit。如果你创建了ComponentB并继承了ComponentA,那么ComponentA的OnInit生命周期将不会触发,直到你显式地调用它,即使你确实为ComponentB拥有这个OnInit生命周期。
调用超级/基本组件方法
为了从ComponentA中获得ngOnInit()方法,我们需要使用super关键字,然后调用我们需要的方法,在本例中是ngOnInit。super关键字指的是要继承的组件的实例,在本例中是ComponentA。
可选择的解决方案:
蒂埃里·坦普利埃的回答是解决这个问题的另一种方法。
在与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的完整示例。