How to extend/inherit components?
我想为已经部署在Angular 2中的一些组件创建扩展,而不必几乎完全重写它们,因为基本组件可能会发生更改,并希望这些更改也反映在其派生组件中。
我创建了这个简单的例子,试图更好地解释我的问题:
使用以下基本组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import {Component, Input} from 'angular2/core'; @Component({ selector: 'base-panel', template: '{{content}}', styles: [` .panel{ padding: 50px; } `] }) export class BasePanelComponent { @Input() content: string; color: string ="red"; onClick(event){ console.log("Click color:" + this.color); } } |
您是否只想更改另一个衍生组件,例如,在示例颜色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | import {Component} from 'angular2/core'; import {BasePanelComponent} from './base-panel.component' @Component({ selector: 'my-panel', template: '{{content}}', styles: [` .panel{ padding: 50px; } `] }) export class MyPanelComponent extends BasePanelComponent{ constructor() { super(); this.color ="blue"; } } |
在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.
正如您在衍生组件
是否有某种方法可以对组件Angular2进行字面上的完全继承,继承标记/注释的
编辑1 - 功能请求
Feature request angular2 added to the project on GitHub: Extend/Inherit angular2 components annotations #7968
编辑2 - 关闭请求
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.
替代方案:
Thierry Templier的答案是解决问题的另一种方法。
在与Thierry Templier提出一些问题之后,我来到了以下工作示例,该示例符合我的期望,作为此问题中提到的继承限制的替代方案:
1 - 创建自定义装饰器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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装饰器的基础组件:
1 2 3 4 5 6 7 8 9 10 | @Component({ // create seletor base for test override property selector: 'master', template: ` Test ` }) export class AbstractComponent { } |
3 - 带@CustomComponent装饰器的子组件:
1 2 3 4 5 6 7 8 9 | @CustomComponent({ // override property annotation //selector: 'sub', selector: (parentSelector) => { return parentSelector + 'sub'} }) export class SubComponent extends AbstractComponent { constructor() { } } |
Plunkr有完整的例子。
Angular 2版本2.3刚刚发布,它包含本机组件继承。看起来你可以继承和覆盖你想要的任何东西,模板和样式除外。一些参考:
-
Angular 2.3中的新功能
-
Angular 2中的组件继承
-
一个Plunkr演示组件继承
现在TypeScript 2.2通过Class表达式支持Mixins,我们有更好的方法来表达Mixins on Components。请注意,您也可以使用组件继承,因为角度2.3(讨论)或自定义装饰器,如此处的其他答案中所讨论的。但是,我认为Mixins有一些属性使它们更适合重用组件之间的行为:
- Mixins组成更灵活,即您可以在现有组件上混合和匹配Mixins,或组合Mixins以形成新组件
- 由于其对类继承层次结构的明显线性化,因此Mixin组合仍然易于理解
- 您可以更轻松地避免装饰器和注释组件继承的注释问题(讨论)
我强烈建议您阅读上面的TypeScript 2.2声明,以了解Mixins的工作原理。角度GitHub问题中的链接讨论提供了更多细节。
你需要这些类型:
1 2 3 4 | export type Constructor< T > = new (...args: any[]) => T; export class MixinRoot { } |
然后你可以声明一个像这个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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 } }; } |
要将
1 2 | export class DashboardComponent extends Destroyable(MixinRoot) implements OnInit, OnDestroy { ... } |
请注意,仅当您想要
在其他情况下,例如当你想在现有的类上组合Mixins时,你可以像这样应用Mixin:
1 | const MyClassWithMixin = MyMixin(MyClass); |
但是,我发现第一种方法最适合
更新
自2.3.0-rc.0起支持组件继承
原版的
到目前为止,对我来说最方便的是将模板和样式保存到单独的
1 2 3 4 5 6 | @Component { selector: 'my-panel', templateUrl: 'app/components/panel.html', styleUrls: ['app/components/panel.css'] } export class MyPanelComponent extends BasePanelComponent |
据我所知,组件继承尚未在Angular 2中实现,我不确定他们是否有计划,但是由于Angular 2使用的是typescript(如果你决定走这条路线),你可以使用类继承通过
我知道这不能回答你的问题,但我真的认为应该避免继承/扩展组件。这是我的推理:
如果由两个或多个组件扩展的抽象类包含共享逻辑:
使用服务甚至创建一个可以在两个组件之间共享的新打字稿类。
如果抽象类...包含共享变量或onClicketc函数,
然后两个扩展组件视图的html之间会有重复。这是不好的做法,共享的html需要分解为组件。这些组件(部件)可以在两个组件之间共享。
我错过了为组件提供抽象类的其他原因吗?
我最近看到的一个例子是扩展AutoUnsubscribe的组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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(),这无论如何都需要额外的逻辑。
让我们了解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。
如果有人正在寻找更新的解决方案,费尔南多的答案非常完美。除了
完整的Custom Decorator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); } } |
然后将其导入新的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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继承,我会建议编写一个自定义装饰器。像下面的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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); } } |