如何扩展/inheritanceAngular2组件?
题
我想为已经部署在Angular 2中的一些组件创build扩展,而不必几乎完全重写它们,因为基本组件可能发生变化,并希望这些变化也反映在派生的组件中。
例
我创build了这个简单的例子,试图解释更好的问题:
使用以下基本组件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); } }
您是否想创build另一个派生组件,例如,在示例颜色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完整的工作示例
注:显然这个例子很简单,可以解决,否则不需要使用inheritance,但它只是为了说明真正的问题。
问题
正如您在派生组件app/my-panel.component.ts
的实现中所看到app/my-panel.component.ts
,大部分实现都是重复的,并且单个部分实际上inheritance了BasePanelComponent
class
,但@Component
基本上必须完全重复,不只是改变的部分,作为selector: 'my-panel'
。
题
通过某种方式来实现组件Angular2的字面完全inheritance,inheritance标记/注释的class
定义,例如@Component
?
编辑1 – function请求
function请求angular2添加到GitHub上的项目: 扩展/inheritanceangular2组件注释#7968
编辑2 – closures的请求
这个请求被closures了, 出于这个原因 ,简单的说就不知道如何合并装饰器了。 离开我们没有select。 所以我的意见是引用在这个问题上 。
Angular 2版本2.3刚刚发布,它包括本地组件inheritance。 它看起来像你可以inheritance和覆盖任何你想要的,除了模板和样式。 一些参考:
-
Angular 2.3的新特性
-
在angular度的组件inheritance2
-
一个演示组件inheritance的Plunkr
替代scheme:
Thierry Templier的这个答案是解决问题的另一种方法。
在Thierry Templier问了一些问题之后,我来到了下面的工作例子,这个例子符合我的期望,作为这个问题中提到的inheritance限制的替代:
1 – 创build自定义装饰器:
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() { } }
普朗克与完整的例子。
据我所知组件inheritance还没有在Angular 2中实现,我不确定他们是否有计划,但是因为Angular 2使用了typescript(如果你已经决定走这条路线),你可以使用类inheritance通过做class MyClass extends OtherClass { ... }
。 对于组件inheritance,我build议通过https://github.com/angular/angular/issues并提交function请求来参与Angular 2项目!
更新
从2.3.0-rc.0开始支持组件inheritance
原版的
到目前为止,对我来说最方便的是将模板和样式保存到单独的*html
& *.css
文件中,并通过templateUrl
和styleUrls
指定它们,因此很容易重用。
@Component { selector: 'my-panel', templateUrl: 'app/components/panel.html', styleUrls: ['app/components/panel.css'] } export class MyPanelComponent extends BasePanelComponent
现在TypeScript 2.2支持通过Classexpression式的Mixins,我们有更好的方法来表示组件上的Mixins。 请注意,您还可以使用组件inheritance,因为angular2.3( 讨论 )或自定义装饰器在此处的其他答案中讨论。 不过,我认为Mixin有一些属性,使它们更适合跨组件重用行为:
- Mixin组成更灵活,也就是说,您可以在现有组件上混合搭配Mixins,或者将Mixin组合起来形成新的组件
- Mixin组合仍然很容易理解,这要归功于类inheritance层次的明显线性化
- 您可以更轻松地避免装饰器和困扰组件inheritance的注释问题( 讨论 )
我强烈build议您阅读上面的TypeScript 2.2公告,了解Mixins如何工作。 angular度GitHub问题中的链接讨论提供了更多的细节。
你需要这些types:
export type Constructor<T> = new (...args: any[]) => T; export class MixinRoot { }
然后你可以像这样声明一个Mixin Destroyable
mixin帮助组件跟踪需要在ngOnDestroy
处理的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 { ... }
请注意,只有当您想要extend
Mixin组合时,才需要MixinRoot
。 你可以很容易地扩展多个混合,例如A extends B(C(D))
。 这是我在上面讨论的mixins的明显线性化,例如,您正在有效地编写一个inheritance层次结构A -> B -> C -> D
。
在其他情况下,例如,当您想要在现有的类上编写Mixin时,可以像这样应用Mixin:
const MyClassWithMixin = MyMixin(MyClass);
不过,我发现第一种方法对Components
和Directives
效果最好,因为它们也需要用@Component
或@Directive
进行修饰。
如果有人正在寻找更新的解决scheme,费尔南多的答案是非常完美的。 除了ComponentMetadata
已被弃用。 使用Component
而不是为我工作。
完整的自定义装饰器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! }
我知道这不能回答你的问题,但我真的认为应该避免inheritance/扩展组件 。 这是我的推理:
如果由两个或多个组件扩展的抽象类包含共享逻辑:使用一个服务,甚至创build一个可以在这两个组件之间共享的新的打字稿类。
如果抽象类…包含共享variables或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(); }); } }
这是因为在整个庞大的代码库中,infiniteSubscriptions.push()仅被使用了10次。 另外,导入和扩展AutoUnsubscribe实际上需要更多的代码,而不仅仅是在组件本身的ngOnDestroy()方法中添加mySubscription.unsubscribe(),这需要额外的逻辑。
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); } }