Как расширить/наследовать компоненты?

Я хотел бы создать расширения для некоторых компонентов, уже развернутых в 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, большая часть реализации была повторена, и единственной частью, которая действительно была унаследована, был class BasePanelComponent, но @Component пришлось в основном полностью повторить, не только измененные части, как selector: 'my-panel'.

Есть ли способ сделать буквально полное наследование компонента Angular2, наследуя определение class маркировки/аннотации, как, например, @Component?

Изменить 1 - Запрос функции

В проект на GitHub добавлен запрос на добавление angular2: аннотации компонентов Extend/Inherit angular2 # 7968

Редактировать 2 - Закрытый запрос

Запрос был закрыт, по этой причине, что в течение короткого времени не будет известно, как объединить декоратор будет сделано. Оставив нас без вариантов. Так что мое мнение цитируется в выпуске.

Ответ 1

Альтернативное решение:

Этот ответ Тьерри Темплиера - альтернативный способ обойти проблему.

После некоторых вопросов с Тьерри Темплиером я пришел к следующему рабочему примеру, который соответствует моим ожиданиям как альтернатива ограничения наследования, упомянутого в этом вопросе:

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

Плункр с полным примером.

Ответ 2

Angular 2 версия 2.3 была только что выпущена, и она включает наследование нативного компонента. Похоже, вы можете наследовать и переопределять все, что хотите, кроме шаблонов и стилей. Некоторые ссылки:

Ответ 3

Теперь, когда TypeScript 2.2 поддерживает Mixins через выражения класса у нас есть намного лучший способ выразить Mixins on Components. Имейте в виду, что вы также можете использовать наследование компонентов с angular 2.3 (обсуждение) или пользовательский декоратор, как описано в других ответах здесь. Тем не менее, я думаю, что у Mixins есть некоторые свойства, которые делают их предпочтительными для повторного использования поведения между компонентами:

  • Микшины сложнее гибки, т.е. вы можете смешивать и сопоставлять Mixins на существующих компонентах или комбинировать Mixins для создания новых компонентов
  • Композиция Mixin остается легкой для понимания благодаря ее очевидной линеаризации в иерархию иерархии классов
  • Вы можете легко избежать проблем с декораторами и аннотациями, которые наследуют наследование компонентов (обсуждение)

Я настоятельно рекомендую вам прочитать анонс TypeScript 2.2 выше, чтобы понять, как работает Mixins. Связанные обсуждения в angular проблемах GitHub предоставляют дополнительную информацию.

Вам понадобятся следующие типы:

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

export class MixinRoot {
}

И тогда вы можете объявить Mixin как этот Destroyable mixin, который помогает компонентам отслеживать подписки, которые нужно удалить в 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
    }
  };
}

Чтобы mixin Destroyable в Component, вы объявляете свой компонент следующим образом:

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

Обратите внимание, что MixinRoot требуется, только если вы хотите extend состав Mixin. Вы можете легко расширить несколько микшинов, например. A extends B(C(D)). Это очевидная линеаризация миксинов, о которых я говорил выше, например. вы эффективно составляете иерархию inheritnace A -> B -> C -> D.

В других случаях, например, если вы хотите создать Mixins в существующем классе, вы можете применить Mixin так:

const MyClassWithMixin = MyMixin(MyClass);

Однако, я нашел, что первый способ лучше всего подходит для Components и Directives, так как они также должны быть украшены @Component или @Directive в любом случае.

Ответ 4

Обновить

Наследование компонентов поддерживается начиная с 2.3.0-rc.0

оригинал

На данный момент наиболее удобным для меня является сохранение шаблона и стилей в отдельных файлах *html & *.css и указание их с помощью templateUrl и styleUrls, чтобы их можно было легко использовать повторно.

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

Ответ 5

Насколько я знаю, наследование компонентов еще не реализовано в Angular 2, и я не уверен, есть ли у них планы, однако, поскольку Angular 2 использует typescript (если вы решили перейдите по этому маршруту), вы можете использовать наследование класса, выполнив class MyClass extends OtherClass { ... }. Для наследования компонентов я предлагаю подключиться к проекту Angular 2, перейдя в https://github.com/angular/angular/issues и отправив запрос функции!

Ответ 6

Я знаю, что это не отвечает на ваш вопрос, но Я действительно считаю, что наследование/расширение компонентов следует избегать. Здесь мои рассуждения:

Если абстрактный класс, расширенный двумя или более компонентами, содержит общую логику: используйте службу или даже создайте новый класс 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();
    });
  }
}

это было базой, потому что во всей большой базе данных, infinSubscriptions.push() использовался только 10 раз. Кроме того, импорт и расширение AutoUnsubscribe на самом деле занимает больше кода, чем просто добавление mySubscription.unsubscribe() в метод ngOnDestroy() самого компонента, что в любом случае потребовало дополнительной логики.

Ответ 7

Если кто-то ищет обновленное решение, ответ Фернандо в значительной степени идеален. За исключением того, что 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!
}

Ответ 8

Компоненты могут быть расширены так же, как наследование классов машинописного текста, просто вы должны переопределить селектор новым именем. Все свойства 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.

Ответ 9

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

Давайте разберемся с некоторыми ключевыми ограничениями и возможностями системы наследования компонентов Angulars.

Компонент наследует только логику класса. Все метаданные в декораторе @Component не наследуются. Свойства компонента @Input и свойства @Output наследуются. Жизненный цикл компонента не наследуется. Эти функции очень важно учитывать, поэтому давайте рассмотрим каждый из них независимо.,

Компонент наследует только логику класса. Когда вы наследуете компонент, вся логика внутри наследуется одинаково. Стоит отметить, что только открытые члены наследуются, поскольку частные члены доступны только в классе, который их реализует. Все метаданные в декораторе @Component не наследуются. Тот факт, что метаданные не наследуются, на первый взгляд может показаться нелогичным, но, если подумать, это действительно имеет смысл. Если вы наследуете от компонента Component (componentA), вы бы не хотели, чтобы селектор ComponentA, от которого вы наследуете, заменил селектор ComponentB, который является классом, который наследует. То же самое можно сказать и о template/templateUrl, а также о style/styleUrls.

Свойства компонента @Input и @Output наследуются

Это еще одна особенность, которая мне очень нравится в Inheritance компонента Angular. В простом предложении, когда у вас есть пользовательские свойства @Input и @Ouput, эти свойства наследуются.

Жизненный цикл компонентов не наследуется. Эта часть не слишком очевидна, особенно для людей, которые не слишком много работали с принципами ООП. Например, скажем, у вас есть ComponentA, который реализует один из множества хуков Angulars, таких как OnInit. Если вы создадите ComponentB и унаследуете ComponentA, жизненный цикл OnInit из ComponentA не сработает, пока вы не вызовете его явно, даже если у вас есть этот жизненный цикл OnInit для ComponentB.

Вызов методов Super/Base Component Чтобы получить метод ngOnInit() из огня ComponentA, нам нужно использовать ключевое слово super, а затем вызвать нужный нам метод, который в данном случае называется ngOnInit. Ключевое слово super ссылается на экземпляр компонента, который наследуется, от которого в этом случае будет ComponentA.

Ответ 11

Для этого вы можете использовать директиву <ng-content></ng-content>.

Например:

Компонент заголовка

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

}

Пользовательский компонент заголовка

@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> печатает Это заголовок

<custom-header-component>This is header</custom-header-component> печатает Это настраиваемый заголовок Это заголовок