web-dev-qa-db-ja.com

コンポーネントを拡張/継承する方法は?

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";
  }
}

Plunkerの完全な動作例

注:明らかに、この例は単純であり、継承を使用する必要がない場合は解決できますが、実際の問題を説明することのみを目的としています。

派生コンポーネントapp/my-panel.component.tsの実装でわかるように、実装の多くが繰り返され、実際に継承された単一の部分はclassBasePanelComponentでしたが、@Componentselector: 'my-panel'のように、変更された部分だけでなく、基本的に完全に繰り返す必要がありました。

@Componentのように、マーキング/注釈のclass定義を継承して、コンポーネントAngular2を文字通り完全に継承する方法はありますか?

編集1-機能リクエスト

GitHubのプロジェクトに追加された機能リクエストangle2: angular2コンポーネントの注釈の拡張/継承#7968

Edit 2-Closed Request

リクエストはクローズされました このため 、デコレータがどのようにマージされるかを簡単に知りません。オプションなしで私たちを残します。だから私の意見は Issueで引用 です。

127
Fernando Leal

代替案:

このThierry Templierの答えは、問題を回避するための代替方法です。

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。

28
Fernando Leal

Angular 2バージョン2.3がリリースされたばかりで、ネイティブコンポーネントの継承も含まれています。テンプレートとスタイルを除いて、必要なものは何でも継承して上書きできます。いくつかの参考文献:

25
Daniel Griscom

TypeScript 2.2 がクラス表現による ミックスインをサポートするようになったので 、より優れたものになりました。コンポーネント上でMixinを表現する方法角度2.3以降のコンポーネントの継承( の説明 )またはここで他の回答で説明しているようなカスタムデコレータも使用できることに注意してください。しかし、Mixinには、コンポーネント間での振る舞いを再利用するのに適したいくつかの特性があると思います。

  • ミックスインはより柔軟に構成します。つまり、既存のコンポーネントのミックスインを組み合わせたり、ミックスインを組み合わせて新しいコンポーネントを作成したりできます。
  • ミックスイン構成は、クラス継承階層への明らかな線形化のおかげで、理解しやすいままです。
  • コンポーネントの継承を悩ますデコレータやアノテーションの問題をより簡単に回避できます( ディスカッション

ミックスインのしくみを理解するために、上記のTypeScript 2.2の発表を読むことを強くお勧めします。 GitHubの問題に関するリンクされた議論はさらなる詳細を提供します。

あなたはこれらのタイプを必要とするでしょう:

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

export class MixinRoot {
}

そして、Destroyableに配置する必要があるサブスクリプションをコンポーネントが追跡するのに役立つ、ngOnDestroy mixinのようなMixinを宣言できます。

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
    }
  };
}

DestroyableComponentにミックスインするには、コンポーネントを次のように宣言します。

export class DashboardComponent extends Destroyable(MixinRoot) 
    implements OnInit, OnDestroy { ... }

MixinRootはMixinコンポジションをextendする場合にのみ必要です。あなたは簡単に複数のmixinを拡張することができます。 A extends B(C(D))。これは私が上で話していたミックスインの明らかな線形化です。継承階層A -> B -> C -> Dを効果的に構成しています。

それ以外の場合は、既存のクラスにMixinを作成したい場合は、Mixinを次のように適用できます。

const MyClassWithMixin = MyMixin(MyClass);

しかし、私は最初の方法がComponentsDirectivesに最適であることを発見しました。これらはとにかく@Componentまたは@Directiveで装飾する必要があるからです。

17

更新

コンポーネントの継承は、 2.3.0-rc.0 以降でサポートされています。

オリジナル

これまでのところ、私にとって最も便利なのは、テンプレートとスタイルを別々の*html*.cssファイルに保存し、それらをtemplateUrlstyleUrlsを通して指定することです。

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

私が知る限り、コンポーネントの継承はまだAngular 2に実装されておらず、計画があるかどうかはわかりませんが、Angular 2はTypeScriptを使用しているため(そのルートに行くことに決めました)class MyClass extends OtherClass { ... }を実行することでクラス継承を使用できます。コンポーネントの継承については、 https://github.com/angular/angular/issues にアクセスして機能リクエストを送信し、Angular 2プロジェクトに参加することをお勧めします。

10
watzon

私はこれがあなたの質問に答えないことを知っていますが、コンポーネントの継承/拡張は避けるべきだと思います。これが私の推論です。

2つ以上のコンポーネントで拡張された抽象クラスに共有ロジックが含まれる場合は、サービスを使用するか、2つのコンポーネント間で共有できる新しいTypeScriptクラスを作成します。

抽象クラス...に共有変数またはonClicketc関数が含まれている場合、2つの拡張コンポーネントビューのhtml間に重複があります。これは悪い習慣で、共有HTMLはコンポーネントに分割する必要があります。これらのコンポーネント(部分)は、2つのコンポーネント間で共有できます。

コンポーネント用の抽象クラスを持つ他の理由が欠けていますか?

最近見た例は、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()を追加するよりも多くのコードを必要とします。

4
robert king

誰かが最新の解決策を探しているなら、Fernandoの答えはかなり完璧です。 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!
}
2
rhyneav

TypeScriptクラスの継承と同じようにコンポーネントを拡張することができます。セレクタを新しい名前でオーバーライドするだけです。親コンポーネントのすべてのInput()およびOutput()プロパティは通常どおり動作します

更新

@Componentはデコレータです。

デコレータは、オブジェクトではなくクラスの宣言中に適用されます。

基本的に、デコレータはクラスオブジェクトにメタデータを追加し、それは継承を介してアクセスすることはできません。

あなたがDecorator Inheritanceを達成したいならば、私はカスタムデコレータを書くことを提案するでしょう。下記の例のようなもの。

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

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);
        }
}
0
mittal bhatt

Angularのコンポーネント継承システムに関するいくつかの重要な制限と機能を理解しましょう。

コンポーネントはクラスロジックのみを継承します@Componentデコレータのすべてのメタデータは継承されませんコンポーネントの@Inputプロパティと@Outputプロパティは継承されませんコンポーネントのライフサイクルは継承されませんこれらの機能は非常に重要です。 。

コンポーネントはクラスロジックのみを継承しますコンポーネントを継承すると、内部のすべてのロジックが等しく継承されます。プライベートメンバーは、それらを実装するクラスでのみアクセス可能であるため、パブリックメンバーだけが継承されることに注意する必要があります。 @Componentデコレータ内のすべてのメタデータは継承されませんメタデータが継承されないという事実は、最初は直観的に思えないかもしれませんが、これについて考えるのは、実際には完全に理にかなっています。 Componentのsay(componentA)から継承する場合、継承元のComponentAのセレクターを継承元のクラスであるComponentBのセレクターに置き換えることは望ましくありません。 template/templateUrlとstyle/styleUrlsについても同じことが言えます。

コンポーネントの@Inputプロパティと@Outputプロパティは継承されます

これは、Angularのコンポーネントの継承について私が本当に気に入っているもう1つの機能です。簡単な文では、カスタムの@Inputプロパティと@Ouputプロパティがあるときはいつでも、これらのプロパティは継承されます。

コンポーネントのライフサイクルは継承されませんこの部分は特にOOPの原則に精通していない人々にはそれほど明白ではないものです。 。たとえば、OnInitのようなAngularの多くのライフサイクルフックの1つを実装するComponentAがあるとします。 ComponentBを作成してComponentAを継承すると、ComponentAのOnInitライフサイクルがあっても、明示的に呼び出すまでComponentAからのOnInitライフサイクルは起動されません。

Super/Baseコンポーネントメソッドの呼び出しComponentAからngOnInit()メソッドを呼び出すには、superキーワードを使用してから、そのメソッドを呼び出す必要があります。この場合ngOnInitである必要があります。 superキーワードは、継承されているコンポーネントのインスタンスを参照します。このインスタンスから継承されるのはComponentAです。

0
Masoud Bimar