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


160

基本コンポーネントは変更される可能性があり、これらの変更が派生コンポーネントにも反映されることを望んでいるため、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);
  }
}

あなたは、例えばのみALTER派生する別の成分、例えば、色の場合の基本的なコンポーネントの動作を作成したいと思います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、実装の多くは繰り返し、そして実際に継承された単一の部分はあったclass BasePanelComponentが、@Componentとして、基本的には完全に繰り返されるだけでなく、変更された部分でなければなりませんでしたselector: 'my-panel'

たとえば、コンポーネントAngular2を文字通り完全に継承しclass、マーキング/アノテーションの定義を継承する方法はあり@Componentますか?

編集1-機能のリクエスト

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

編集2-終了したリクエスト

この理由により、デコレータをマージする方法が簡単にわからないというリクエストはクローズされます。オプションなしで私たちを残します。だから私の意見は問題に引用されています。


この回答をチェックstackoverflow.com/questions/36063627/...よろしく
NicolasB

OKニコラスB. しかし、私の問題は、継承メタデータに適用されないデコレータ@Componentの継承にあります。= /
フェルナンドリール2016

人々は、角度付きの継承の使用を避けてください。たとえば、エクスポートクラスPlannedFilterComponent extends AbstractFilterComponent implements OnInit {is bad bad。サービスや小さなコンポーネントなど、コードを共有する他の方法があります。継承は角度のある方法ではありません。私は彼らが継承を使用している角度のあるプロジェクトにいて、抽象クラスの入力が欠落している抽象コンポーネントから継承するコンポーネントをエクスポートするなど、壊れるものがあります。
ロバート・キング2017

1
代わりにコンテンツプロジェクションを使用してください。例:github.com/angular/components/blob/master/src/material/card/…継承を使用しないでください
robert king

回答:


39

代替ソリューション:

ティエリーテンプリエのこの答えは、問題を回避する別の方法です。

ティエリーテンプリエとのいくつかの質問の後、私はこの質問で述べられた継承の制限の代わりとしての私の期待に応える次の実用的な例に行きました:

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。


3
これはオフラインテンプレートコンパイラと互換性がないと思います。
ギュンターZöchbauer

@GünterZöchbauer、Angular2の「オフラインコンパイラテンプレート」については知りません。しかし、私はそれが互換性がないかもしれないと思います、そしてそれは代替の選択肢になるでしょう。Angular2の「オフラインテンプレートコンパイラ」モードが役立つのはどこですか?これについて理解を深めるために何かを見せていただけますか?私のプロジェクトにとって、この互換性の重要性を理解できます。
Fernando Leal

オフラインテンプレートコンパイラ(OTC)は、RC.3に既に含まれていますが、まだ機能していません。OTCはデコレータを分析し、デプロイ可能なコードが生成されるビルドステップ中にコードを生成します。OTCでは、実行時にデコレーターとバインディングを処理するAngular2パーサーとコンパイラーを削除できます。これにより、コードサイズが大幅に小さくなり、アプリケーションとコンポーネントの初期化が高速になります。OTCは、おそらく次の更新のいずれかで使用可能になるでしょう。
ギュンターZöchbauer

1
@GünterZöchbauer、OTCと互換性のある機能を維持することの重要性を理解しました。これは、コンポーネントを初期化するためのオーバーヘッドを削減する角度デコレータのプリコンパイルになります。このプロセスの機能について知りたいのですが、この回答の解決策はOTCと互換性がないためですか?デコレータのプリコンパイルはどうですか?この知識があれば、OTCの代わりにこの機能を維持するために何かを考えるかもしれません。説明していただきありがとうございます!
Fernando Leal

24

Angular 2バージョン2.3がリリースされたばかりで、ネイティブコンポーネントの継承が含まれています。テンプレートとスタイルを除いて、何でも継承してオーバーライドできるようです。いくつかの参照:


子コンポーネントで新しい「セレクター」を指定し忘れた場合、ここで1つの「問題」が発生します。More than one component matched on this elementそうしないと、に沿ってランタイムエラーが発生します。
Aelfinn 2017年

@TheAelfinnええ:各コンポーネントには@Component()タグ内に完全な仕様が必要です。ただし、必要に応じて、同じファイルを参照して.htmlまたは.cssを共有できます。全体として、それは大きなプラスです。
Daniel Griscom、2017年

2番目のリンクscotch.io/tutorials/component-inheritance-in-angular-2で、著者はコンポーネントが親の依存性注入サービスを継承すると主張していますが、私のコードはそうではないことを示唆しています。これがサポートされていることを確認できますか?
Aelfinn 2017年

18

今では活字体2.2をサポートするクラス表現によるミックスイン我々がコンポーネント上でミックスインを表現するためのより良い方法を持っています。ここで他の回答で説明されているように、angular 2.3(ディスカッション)以降のコンポーネントの継承またはカスタムデコレータを使用することもできます。ただし、Mixinsには、コンポーネント間で動作を再利用するために好ましいいくつかのプロパティがあると思います。

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

上記のTypeScript 2.2の発表を読んで、Mixinsの動作を理解することを強くお勧めします。角度のあるGitHubの問題に関するリンクされたディスカッションは、さらに詳細を提供します。

次のタイプが必要です。

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

export class MixinRoot {
}

次に、このDestroyableミックスインのようなミックスインを宣言して、コンポーネントを破棄する必要があるサブスクリプションを追跡できるようにし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 { ... }

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

その他の場合、例えば、既存のクラスでMixinを作成したい場合、次のようにMixinを適用できます:

const MyClassWithMixin = MyMixin(MyClass);

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


これはすごい!提案をありがとう。ここでMixinRootは単に空のクラスプレースホルダーとして使用されているだけですか?私の理解が正しいことを確認したいだけです。
Alex Lockwood 2017年

@AlexLockwoodうん、空のクラスプレースホルダーはまさに私がそれを使用しているものです。私は喜んでそれを使用しないようにしますが、今のところそれを行うためのより良い方法を見つけていません。
ヨハネスルドルフ

2
私は結局使用しましたfunction Destroyable<T extends Constructor<{}>>(Base = class { } as T)。そうすれば、を使用してミックスインを作成できますextends Destroyable()
Alex Lockwood 2017年

1
これは非常によく見えますが、AoTビルド(Cli1.3)は呼び出されないため、ngOnDestroyをDashBoardComponentから削除しているようです。(ngOnInitも同様)
dzolnjan 2017

この解決策をありがとう。ただし、ionicまたはangular-cliを使用して製品をビルドした後は、拡張されていないかのように、ミックスインが何らかの方法で機能しません。
ハンチェ

16

更新

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

2
これはまさに私が必要とするものです。BasePanelComponentの@Componentデコレータはどのように見えますか?異なるhtml / cssファイルを参照できますか?MyPanelComponentによって参照される同じhtml / cssファイルを参照できますか?
ebhh2001

1
これは継承@Input()@Output()デコレータではありませんか?
Leon Adler

10

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


了解しました。次の日、angular2プロジェクトを繰り返して、リクエスト機能がGitのプロジェクトの問題に含まれていないことを確認します。そうでない場合は、リソースのリクエストを作成します。特徴。最も興味深い要求をするための追加の議論のアイデアはありますか?
Fernando Leal

1
私が最初のソリューションで既に使用している継承リソースのタイプスクリプト(export class MyPanelComponent extends BasePanelComponent)に関しては、問題はアノテーション/デコレーターが継承されない場合のみです。
Fernando Leal

1
ええ、私はあなたが他に何を追加できるか本当にわかりません。私@SubComponent()は、クラスをサブコンポーネントとしてマークする新しいデコレータ(のようなもの)を持つか@Component、継承元の親コンポーネントを参照できるようにするデコレータにフィールドを追加するというアイデアが好きです。
ワトソン

1
機能リクエストangular2がGitHubのプロジェクトに追加されました:angular2コンポーネントアノテーションの拡張/継承#7968
Fernando Leal

9

Angularのコンポーネント継承システムの主な制限と機能を理解しましょう。

コンポーネントはクラスロジックのみを継承します。

  • @Componentデコレーターのすべてのメタデータは継承されません。
  • コンポーネントの@Inputプロパティと@Outputプロパティは継承されます。
  • コンポーネントのライフサイクルは継承されません。

これらの機能は覚えておくことが非常に重要なので、それぞれを個別に調べてみましょう。

コンポーネントはクラスロジックのみを継承します

コンポーネントを継承すると、内部のすべてのロジックが等しく継承されます。プライベートメンバーは、それらを実装するクラスでのみアクセスできるため、パブリックメンバーのみが継承されることに注意してください。

@Componentデコレーターのすべてのメタデータは継承されません

メタデータが継承されないという事実は、最初は直観に反するように思えるかもしれませんが、これについて考えると、実際には完全に理にかなっています。Component say(componentA)から継承する場合、継承元のComponentAのセレクターが、継承しているクラスであるComponentBのセレクターを置き換えることは望ましくありません。同じことは、template / templateUrlおよびstyle / styleUrlsについても言えます。

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

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

コンポーネントのライフサイクルは継承されません

この部分は、特にOOPの原則を広範囲に使用していない人にはそれほど明白ではありません。たとえば、OnInitのようなAngularの多くのライフサイクルフックの1つを実装するComponentAがあるとします。ComponentBを作成してComponentAを継承する場合、ComponentBにこのOnInitライフサイクルがある場合でも、ComponentAからのOnInitライフサイクルは、明示的に呼び出すまで起動しません。

スーパー/ベースコンポーネントメソッドの呼び出し

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


5

CDKライブラリとマテリアルライブラリを読んだ場合、それらは継承を使用していますが、コンポーネント自体にはそれほど多くは使用していません。コンテンツプロジェクションがIMOの主役です。このリンクhttps://blog.angular-university.io/angular-ng-content/を参照してください「このデザインの主要な問題」とあります

これであなたの質問に答えられないことはわかっていますが、コンポーネントの継承/拡張は避けた方いいと思います。ここに私の推論があります:

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には、コンポーネント自体のメソッドを追加mySubscription.unsubscribe()するだけでなく、実際にはngOnDestroy()追加のロジックが必要なコードよりも多くのコードが必要です。


わかりました、私はあなたのコロケーションを理解し、集約が継承を必要とするように見えるすべての問題をほぼ解決することに同意します。また、コンポーネントを、さまざまな方法でドッキングできるアプリケーションの小さな部分と考えることは常に興味深いことです。しかし、質問の場合、問題は、継承したいコンポーネント(3番目のコンポーネント)の変更に対する制御/アクセス権がないため、集約が実行不可能になり、継承が理想的な解決策になるということです。
Fernando Leal 2017

サードパーティのコンポーネントをカプセル化する新しいコンポーネントを作成できないのはなぜですか?興味のないサードパーティのコンポーネントは何ですか?例:<my-calendar [stuff] = stuff> <third-party-calendar [stuff] = stuff> </ ..> </ ..>
robert king 2017

@robertking自分を繰り返すのは非常に弱いパターンです...それがあなたがそれを楽しむのではなく、あなたの仕事を嫌いになり始める理由です。
Dariusz Filipiak

私については、コンポーネントのセットに対して同じ入力/出力パラメーターを持たせたい場合に備えて、コンポーネントを拡張することをお勧めします。たとえば、いくつかの登録手順(credentialsStep、addressStep、selectBenefitsStep)があります。それらはすべて同じ入力オプション(stepName、actionButtons ...)と出力(complete、cancel)を持つ必要があります。
Sergey_T

@Sergey_T ng選択とコンテンツプロジェクションを備えた1つのコンポーネントを検討できますか?また、いくつかの入力を繰り返すことは、TBHの多くの機能を実際に節約しているようには見えません。
ロバートキング

2

誰かが更新されたソリューションを探しているなら、フェルナンドの答えはかなり完璧です。それ以外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!
}

カスタムデコレータはあまり推奨されていませんか?他の多くの投稿/スレッドから、AOTがそれらをサポートしないため、このソリューションは完全に間違っているとマークされていますか?
TerNovi 2018年

2

@ Input、@ Output、@ ViewChildなどを継承できます。サンプルを見てください。

@Component({
    template: ''
})
export class BaseComponent {
    @Input() someInput: any = 'something';

    @Output() someOutput: EventEmitter<void> = new EventEmitter<void>();

}

@Component({
    selector: 'app-derived',
    template: '<div (click)="someOutput.emit()">{{someInput}}</div>',
    providers: [
        { provide: BaseComponent, useExisting: DerivedComponent }
    ]
})
export class DerivedComponent {

}

1

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

更新

@Componentはデコレータです。

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

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

デコレータの継承を実現したい場合は、カスタムデコレータを書くことをお勧めします。以下の例のようなもの。

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


(質問の例を使用して)それがどのように機能するかを例示できますか?stackblitzを使用してサンプルを開発し、リンクを共有できます。
フェルナンドリール2018年

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

あなたが正しいです。デコレータは何の違いもありません。ベースコンポーネントが別の場所でコンポーネントとして使用されている場合にのみ必要です
MAHESH VALIYA VEETIL

0
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);
        }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.