首页 文章

如何扩展/继承Angular2组件?

提问于
浏览
107

Question

我想为已经部署在Angular 2中的一些组件创建扩展,而不必几乎完全重写它们,因为基本组件可能会发生更改,并希望这些更改也反映在其派生组件中 .

Example

I created this simple example to try to explain better my questions:

使用以下基本组件 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

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

Problem

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

Question

以某种方式对组件Angular2进行字面上的完全继承,继承标记/注释的 class 定义,例如 @Component

Edit 1 - Feature Request

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

Edit 2 - Closed Request

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

10 回答

  • 25

    Alternative Solution:

    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() {
      }
    }
    

    Plunkr with complete example.

  • 12

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

  • 12

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

    • Mixins组合更灵活,即您可以在现有组件上混合和匹配Mixins,或组合Mixins以形成新组件

    • Mixin组合仍然易于理解,因为它对类继承层次结构有明显的线性化

    • 您可以更轻松地避免使用装饰器和注释组件继承的注释(discussion

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

    你需要这些类型:

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

    然后你可以声明一个类似于这个 Destroyable mixin的Mixin,它可以帮助组件跟踪需要在_1440882中处理的订阅:

    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 . 您可以轻松扩展多个mixin,例如 A extends B(C(D)) . 这是我上面谈到的mixins的明显线性化,例如你正在有效地组成一个继承层次结构 A -> B -> C -> D .

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

    const MyClassWithMixin = MyMixin(MyClass);
    

    但是,我发现第一种方式最适合 ComponentsDirectives ,因为这些也需要用 @Component@Directive 进行修饰 .

  • 24

    update

    2.3.0-rc.0支持组件继承

    original

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

    @Component {
        selector: 'my-panel',
        templateUrl: 'app/components/panel.html', 
        styleUrls: ['app/components/panel.css']
    }
    export class MyPanelComponent extends BasePanelComponent
    
  • -1

    据我所知,组件继承尚未在Angular 2中实现,并且我决定采用该路由)您可以通过执行 class MyClass extends OtherClass { ... } 来使用类继承 . 对于组件继承,我建议通过转到https://github.com/angular/angular/issues并提交功能请求参与Angular 2项目!

  • 1

    我知道这不能回答你的问题但是 I really think inheriting / extending components should be avoided . 这是我的推理:

    如果由两个或多个组件扩展的抽象类包含共享逻辑:使用服务或甚至创建可在两个组件之间共享的新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(),这无论如何都需要额外的逻辑 .

  • 0

    如果有人正在寻找更新的解决方案,费尔南多的答案非常完美 . 除了 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!
    }
    
  • 3

    组件可以像typescript类继承一样扩展,只需要用新名称覆盖选择器 . 父组件中的所有Input()和Output()属性都正常工作

    Update

    @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

  • 2
    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);
            }
    }
    
  • 10

    您可以使用 <ng-content></ng-content> 指令 .

    例如:

    Header Component

    @Component({
          selector: 'header-component',
          template: `
                     <header>
                         <ng-content><ng-content>
                     </header>
                    `
    })
    export class HeaderComponent {
    
    }
    

    Custom Header Component

    @Component({
          selector: 'custom-header-component',
          template: `
                     <header-component>
                         This is custom header component
                         <ng-content></ng-content>
                     </header-component>
                    `
    })
    export class CustomHeaderComponent {
    
    }
    

    <header-component>This is header</header-component> 打印 This is header

    <custom-header-component>This is header</custom-header-component> 打印 This is custom header This is header

相关问题