web-dev-qa-db-fra.com

Comment étendre / hériter des composants?

Je souhaite créer des extensions pour certains composants déjà déployés dans Angular 2, sans avoir à les réécrire presque complètement, car le composant de base pourrait subir des modifications et souhaiter que ces modifications soient également reflétées dans ses composants dérivés.

J'ai créé cet exemple simple pour essayer de mieux expliquer mes questions:

Avec le composant de base suivant 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);
  }
}

Souhaitez-vous créer un autre composant dérivé uniquement modifier, par exemple, un comportement de composant de base dans le cas de la couleur de l'exemple, 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";
  }
}

Exemple de travail complet dans Plunker

Remarque: Évidemment, cet exemple est simple et pourrait être résolu sinon il n'est pas nécessaire d'utiliser l'héritage, mais il ne vise qu'à illustrer le problème réel.

Comme vous pouvez le constater dans l'implémentation du composant dérivé app/my-panel.component.ts, une grande partie de l'implémentation a été répétée et la partie unique réellement héritée était le classBasePanelComponent, mais le @Component a dû être complètement répété, pas seulement les parties modifiées, comme le selector: 'my-panel'.

Existe-t-il un moyen de faire un héritage littéralement complet d'un composant Angular2, en héritant de la définition class des marques/annotations, comme par exemple @Component?

Éditer 1 - Demande de fonctionnalité

Demande de fonctionnalité angular2 ajoutée au projet sur GitHub: étendre/hériter des annotations des composants angular2 # 7968

Éditer 2 - Requête fermée

La demande a été fermée, pour cette raison , que brièvement ne sauront pas comment fusionner le décorateur sera faite. Nous laissant sans options. Donc, mon opinion est cité dans le numéro .

127
Fernando Leal

Solution alternative:

Cette réponse de Thierry Templier est un moyen alternatif de résoudre le problème.

Après quelques questions avec Thierry Templier, je suis arrivé à l'exemple de travail suivant qui répond à mes attentes en tant qu'alternative à la limitation de l'héritage mentionnée dans cette question:

1 - Créer un décorateur personnalisé:

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 - Composant de base avec le décorateur @Component:

@Component({
  // create seletor base for test override property
  selector: 'master',
  template: `
    <div>Test</div>
  `
})
export class AbstractComponent {

}

3 - Sous composant avec le décorateur @CustomComponent:

@CustomComponent({
  // override property annotation
  //selector: 'sub',
  selector: (parentSelector) => { return parentSelector + 'sub'}
})
export class SubComponent extends AbstractComponent {
  constructor() {
  }
}

Plunkr avec exemple complet.

30
Fernando Leal

Angular 2 version 2.3 vient de paraître et inclut l'héritage des composants natifs. Il semble que vous puissiez hériter et remplacer tout ce que vous voulez, à l'exception des modèles et des styles. Quelques références:

25
Daniel Griscom

Maintenant que TypeScript 2.2 prend en charge Mixins via des expressions de classe , nous disposons d’un moyen bien plus efficace pour exprimer Mixins sur des composants. Notez que vous pouvez également utiliser l'héritage de composant depuis angular 2.3 ( discussion ) ou un décorateur personnalisé, comme indiqué dans d'autres réponses ici. Cependant, je pense que les Mixins ont certaines propriétés qui les rendent préférables pour la réutilisation du comportement entre les composants:

  • Les Mixins composent une composition plus flexible, c’est-à-dire que vous pouvez mélanger et assortir des Mixins sur des composants existants ou combiner des Mixins pour former de nouveaux composants.
  • La composition de Mixin reste facile à comprendre grâce à sa linéarisation évidente vers une hiérarchie d'héritage de classe
  • Vous pouvez plus facilement éviter les problèmes avec les décorateurs et les annotations qui minent l'héritage des composants ( discussion )

Je vous suggère fortement de lire l'annonce de TypeScript 2.2 ci-dessus pour comprendre le fonctionnement de Mixins. Les discussions liées dans angular questions GitHub fournissent des détails supplémentaires.

Vous aurez besoin de ces types:

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

export class MixinRoot {
}

Et ensuite, vous pouvez déclarer un Mixin comme ceci Destroyable mixin qui aide les composants à garder une trace des abonnements devant être supprimés dans 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
    }
  };
}

Pour mixer Destroyable dans un Component, vous déclarez votre composant comme suit:

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

Notez que MixinRoot n'est nécessaire que si vous voulez extend une composition Mixin. Vous pouvez facilement étendre plusieurs mixins, par exemple. A extends B(C(D)). C’est la linéarisation évidente des mixins dont je parlais plus haut, par exemple. vous composez effectivement une hiérarchie inheritnace A -> B -> C -> D.

Dans d'autres cas, par exemple quand vous voulez composer des Mixins sur une classe existante, vous pouvez appliquer le Mixin comme suit:

const MyClassWithMixin = MyMixin(MyClass);

Cependant, j’ai trouvé que la première méthode fonctionnait mieux pour Components et Directives, car elles doivent également être décorées avec @Component ou @Directive de toute façon.

17
Johannes Rudolph

mettre à jour

L'héritage des composants est supporté depuis 2.3.0-rc.

original

Jusqu'ici, le plus pratique pour moi est de conserver le modèle et les styles dans des fichiers *html et *.css distincts et de les spécifier via templateUrl et styleUrls, afin de pouvoir les réutiliser facilement.

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

Pour autant que je sache, l'héritage des composants n'a pas encore été implémenté dans Angular 2 et je ne sais pas s'ils en ont l'intention, cependant, étant donné que Angular 2 utilise TypeScript (si vous ' Nous avons décidé de suivre cette voie) vous pouvez utiliser l’héritage de classe en faisant class MyClass extends OtherClass { ... }. Pour l'héritage de composants, je vous suggèrerais de vous impliquer dans le projet Angular 2 en accédant à https://github.com/angular/angular/issues et en soumettant une demande de fonctionnalité!

10
watzon

Je sais que cela ne répond pas à votre question mais Je pense vraiment que les composants hérités/étendus devraient être évités. Voici mon raisonnement:

Si la classe abstraite étendue de deux composants ou plus contient une logique partagée: utilisez un service ou créez même une nouvelle classe TypeScript pouvant être partagée entre les deux composants.

Si la classe abstraite ... contient des variables partagées ou des fonctions onClicketc, il y aura alors duplication entre le code HTML des deux vues de composants d'extension. Ceci est une mauvaise pratique et que le code HTML partagé doit être divisé en Composant (s). Ces composants (pièces) peuvent être partagés entre les deux composants.

Me manque-t-il d'autres raisons d'avoir une classe abstraite pour les composants?

Un exemple que j'ai vu récemment concerne les composants qui prolongent 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();
    });
  }
}

c'était dû au fait que, dans une base de code volumineuse, infiniteSubscriptions.Push () n'était utilisé que 10 fois. De plus, importer et étendre AutoUnsubscribe nécessite en réalité plus de code que simplement ajouter mySubscription.unsubscribe () à la méthode ngOnDestroy () du composant lui-même, ce qui nécessitait de toute façon une logique supplémentaire.

5
robert king

Si quelqu'un cherche une solution actualisée, la réponse de Fernando est quasiment parfaite. Sauf que ComponentMetadata est obsolète. Utiliser Component à la place a fonctionné pour moi.

Le fichier complet de Custom Decorator CustomDecorator.ts se présente comme suit:

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

Puis importez-le dans votre nouveau fichier de composants sub-component.component.ts et utilisez @CustomComponent au lieu de @Component comme ceci:

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

Laissez-nous comprendre certaines limitations et caractéristiques clés du système d’héritage de composants Angular.

Le composant hérite uniquement de la logique de classe. Toutes les métadonnées du décorateur @Component ne sont pas héritées. Propriété Component @Input et les propriétés @Output sont héritées. Le cycle de vie du composant n'est pas hérité. .

Le composant n'hérite que de la logique de classe Lorsque vous héritez d'un composant, toute la logique est héritée de la même manière. Il est à noter que seuls les membres publics sont hérités, car les membres privés ne sont accessibles que dans la classe qui les implémente. Toutes les méta-données du décorateur @Component ne sont pas héritées Le fait qu'aucune méta-donnée ne soit héritée peut sembler contre-intuitif au début, mais si vous y réfléchissez, cela prend tout son sens. Si vous héritez d'un composant dit (composantA), vous ne voudriez pas que le sélecteur de ComponentA, dont vous héritez, remplace le sélecteur de ComponentB, qui est la classe qui hérite. La même chose peut être dite pour le template/templateUrl ainsi que pour le style/styleUrls.

les propriétés Component @Input et @Output sont héritées

C’est une autre caractéristique que j’aime beaucoup dans la composante Inheritance in Angular. En une phrase simple, chaque fois que vous avez une propriété @Input et @Ouput personnalisée, ces propriétés sont héritées.

Le cycle de vie d'un composant n'est pas hérité Cette partie est celle qui n'est pas si évidente en particulier pour les personnes qui n'ont pas beaucoup travaillé avec les principes OOP. Par exemple, supposons que ComponentA implémente l’un des nombreux points d'ancrage du cycle de vie d'Angular comme OnInit. Si vous créez ComponentB et héritez de ComponentA, le cycle de vie OnInit de ComponentA ne se déclenchera pas jusqu'à ce que vous l'appeliez explicitement, même si vous avez ce cycle de vie OnInit pour ComponentB.

Appel de méthodes de composant Super/Base Pour pouvoir utiliser la méthode ngOnInit () de ComponentA, nous devons utiliser le mot-clé super, puis appeler la méthode dont nous avons besoin, qui est dans ce cas ngOnInit. Le mot-clé super fait référence à l'instance du composant dont on hérite et qui, dans ce cas, sera ComponentA.

1
Masoud Bimar

Les composants peuvent être étendus de la même manière qu’un héritage de classe TypeScript, mais vous devez simplement remplacer le sélecteur par un nouveau nom. Toutes les propriétés Input () et Output () du composant parent fonctionnent normalement.

Mettre à jour

@Component est un décorateur,

Les décorateurs sont appliqués lors de la déclaration de classe pas sur les objets.

Fondamentalement, les décorateurs ajoutent des métadonnées à l'objet de classe et sont inaccessibles via l'héritage.

Si vous souhaitez réaliser l'héritage de décorateur, je vous suggérerais d'écrire un décorateur personnalisé. Quelque chose comme exemple ci-dessous.

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

Voir: 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