我想为已经部署在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);
}
}
您是否要创建另一个衍生组件,例如,在示例颜色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
注意:显然这个例子很简单,可以解决,否则不需要使用继承,但它只是为了说明真正的问题。
正如您在衍生组件app/my-panel.component.ts
的实现中所看到的,大部分实现都是重复的,单个部分真正继承的是class
BasePanelComponent
,但@Component
基本上必须完全重复,而不仅仅是变化的部分,如selector: 'my-panel'
。
是否有某种方法可以对组件Angular2进行字面上的完全继承,继承标记/注释的class
定义,例如@Component
?
编辑1 - 功能请求
功能请求angular2添加到GitHub上的项目:Extend/Inherit angular2 components annotations #7968
编辑2 - 关闭请求
请求被关闭,for this reason,暂时不知道如何合并装饰器将被制作。离开我们没有选择。所以我的观点是quoted in the Issue。
替代方案:
This answer of Thierry Templier is an alternative way to get around the problem.
在与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() {
}
}
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);
}
}
Angular 2版本2.3刚刚发布,它包含本机组件继承。看起来你可以继承和覆盖你想要的任何东西,模板和样式除外。一些参考:
既然TypeScript 2.2支持Mixins through Class expressions,我们有更好的方式来表达Mixins on Components。请注意,您也可以使用组件继承,因为角度为2.3(discussion)或自定义装饰器,如此处的其他答案中所述。但是,我认为Mixins有一些属性使它们更适合重用组件之间的行为:
我强烈建议您阅读上面的TypeScript 2.2声明,以了解Mixins的工作原理。角度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
}
};
}
要将Destroyable
混合到Component
中,您可以像这样声明您的组件:
export class DashboardComponent extends Destroyable(MixinRoot)
implements OnInit, OnDestroy { ... }
请注意,MixinRoot
仅在您需要extend
Mixin组合时才需要。您可以轻松扩展多个mixin,例如A extends B(C(D))
。这是我上面谈到的mixins的明显线性化,例如你有效地组成了一个继承层次结构A -> B -> C -> D
。
在其他情况下,例如当你想在现有的类上组合Mixins时,你可以像这样应用Mixin:
const MyClassWithMixin = MyMixin(MyClass);
然而,我发现第一种方式最适合Components
和Directives
,因为这些也需要用@Component
或@Directive
装饰。
更新
自2.3.0-rc.0以来,支持组件继承
原版的
到目前为止,对我来说最方便的是将模板和样式保存到单独的*html
和*.css
文件中,并通过templateUrl
和styleUrls
指定它们,因此它很容易重复使用。
@Component {
selector: 'my-panel',
templateUrl: 'app/components/panel.html',
styleUrls: ['app/components/panel.css']
}
export class MyPanelComponent extends BasePanelComponent
据我所知,组件继承尚未在Angular 2中实现,我不确定他们是否有计划,但是由于Angular 2使用的是typescript(如果你决定走这条路线),你可以使用类继承通过做class MyClass extends OtherClass { ... }
。对于组件继承,我建议通过访问https://github.com/angular/angular/issues并提交功能请求来参与Angular 2项目!
我知道这不能回答你的问题,但我真的认为应该避免继承/扩展组件。这是我的推理:
如果由两个或多个组件扩展的抽象类包含共享逻辑:使用服务或甚至创建可在两个组件之间共享的新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();
});
}
}
这是bas,因为在整个大型代码库中,infiniteSubscriptions.push()只使用了10次。另外,导入和扩展AutoUnsubscribe实际上需要的代码多于在组件本身的ngOnDestroy()方法中添加mySubscription.unsubscribe(),这无论如何都需要额外的逻辑。
如果有人正在寻找更新的解决方案,费尔南多的答案非常完美。除了ComponentMetadata
已被弃用。使用Component
代替我工作。
完整的Custom Decorator CustomDecorator.ts
文件如下所示:
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);
}
}
然后将其导入到您的新组件sub-component.component.ts
文件中,并使用@CustomComponent
而不是@Component
,如下所示:
import { CustomComponent } from './CustomDecorator';
import { AbstractComponent } from 'path/to/file';
...
@CustomComponent({
selector: 'subcomponent'
})
export class SubComponent extends AbstractComponent {
constructor() {
super();
}
// Add new logic here!
}
组件可以像typescript类继承一样扩展,只需要用新名称覆盖选择器。父组件中的所有Input()和Output()属性都正常工作
更新
@Component是装饰者,
在类的声明期间,不在对象上应用装饰器。
基本上,装饰器会向类对象添加一些元数据,并且无法通过继承进行访问。
如果你想实现Decorator继承,我会建议编写一个自定义装饰器。像下面的例子。
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);
};
};
参考:https://medium.com/@ttemplier/angular2-decorators-and-class-inheritance-905921dbd1b7
让我们了解Angular的组件继承系统的一些关键限制和特性。
组件只继承类逻辑@Component装饰器中的所有元数据都不是继承的组件@Input属性和@Output属性是继承的组件生命周期不是继承的这些功能非常重要,所以让我们独立地检查每一个。
Component仅继承类逻辑当您继承Component时,内部的所有逻辑都是同等继承的。值得注意的是,只有公共成员才会被继承,因为私有成员只能在实现它们的类中访问。 @Component装饰器中的所有元数据都不是继承的。没有元数据被继承的事实最初可能看起来是反直觉的,但是,如果你想到这一点,它实际上是非常有意义的。如果从Component say(componentA)继承,则不希望继承的ComponentA的选择器替换ComponentB的选择器,ComponentB是继承的类。对于template / templateUrl以及style / styleUrls,也可以这样说。
组件@Input和@Output属性是继承的
这是另一个我非常喜欢Angular中的组件继承的功能。简单地说,只要有自定义的@Input和@Ouput属性,就会继承这些属性。
组件生命周期不是继承的这部分对于那些没有广泛使用OOP原则的人来说尤其不明显。例如,假设您有ComponentA实现Angular的许多生命周期钩子之一,如OnInit。如果您创建ComponentB并继承ComponentA,则即使您确实具有ComponentB的OnInit生命周期,ComponentA的OnInit生命周期也不会触发,直到您显式调用它为止。
调用超级/基本组件方法为了从ComponentA中获取ngOnInit()方法,我们需要使用super关键字,然后调用我们需要的方法,在本例中为ngOnInit。 super关键字指的是继承的组件的实例,在这种情况下将是ComponentA。