How to extend / inherit components?

后端 未结 12 813
萌比男神i
萌比男神i 2020-12-02 04:11

I would like to create extensions for some components already deployed in Angular 2, without having to rewrite them almost completely, as the base component could undergo ch

相关标签:
12条回答
  • 2020-12-02 04:53

    Now that TypeScript 2.2 supports Mixins through Class expressions we have a much better way to express Mixins on Components. Mind you that you can also use Component inheritance since angular 2.3 (discussion) or a custom decorator as discussed in other answers here. However, I think Mixins have some properties that make them preferable for reusing behavior across components:

    • Mixins compose more flexibly, i.e. you can mix and match Mixins on existing components or combine Mixins to form new Components
    • Mixin composition remains easy to understand thanks to its obvious linearization to a class inheritance hierarchy
    • You can more easily avoid issues with decorators and annotations that plague component inheritance (discussion)

    I strongly suggest you read the TypeScript 2.2 announcement above to understand how Mixins work. The linked discussions in angular GitHub issues provide additional detail.

    You'll need these types:

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

    And then you can declare a Mixin like this Destroyable mixin that helps components keep track of subscriptions that need to be disposed in 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
        }
      };
    }
    

    To mixin Destroyable into a Component, you declare your component like this:

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

    Note that MixinRoot is only necessary when you want to extend a Mixin composition. You can easily extend multiple mixins e.g. A extends B(C(D)). This is the obvious linearization of mixins I was talking about above, e.g. you're effectively composing an inheritnace hierarchy A -> B -> C -> D.

    In other cases, e.g. when you want to compose Mixins on an existing class, you can apply the Mixin like so:

    const MyClassWithMixin = MyMixin(MyClass);
    

    However, I found the first way works best for Components and Directives, as these also need to be decorated with @Component or @Directive anyway.

    0 讨论(0)
  • 2020-12-02 04:55

    update

    Component inheritance is supported since 2.3.0-rc.0

    original

    So far, the most convenient for me is to keep template & styles into separate *html & *.css files and specify those through templateUrl and styleUrls, so it's easy reusable.

    @Component {
        selector: 'my-panel',
        templateUrl: 'app/components/panel.html', 
        styleUrls: ['app/components/panel.css']
    }
    export class MyPanelComponent extends BasePanelComponent
    
    0 讨论(0)
  • 2020-12-02 04:58

    You can inherit @Input, @Output, @ViewChild, etc. Look at the sample:

    @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 {
    
    }
    
    0 讨论(0)
  • 2020-12-02 05:00

    Let us understand some key limitations & features on Angular’s component inheritance system.

    The component only inherits the class logic:

    • All meta-data in the @Component decorator is not inherited.
    • Component @Input properties and @Output properties are inherited.
    • Component lifecycle is not inherited.

    These features are very important to have in mind so let us examine each one independently.

    The Component only inherits the class logic

    When you inherit a Component, all logic inside is equally inherited. It is worth noting that only public members are inherited as private members are only accessible in the class that implements them.

    All meta-data in the @Component decorator is not inherited

    The fact that no meta-data is inherited might seem counter-intuitive at first but, if you think about this it actually makes perfect sense. If you inherit from a Component say (componentA), you would not want the selector of ComponentA, which you are inheriting from to replace the selector of ComponentB which is the class that is inheriting. The same can be said for the template/templateUrl as well as the style/styleUrls.

    Component @Input and @Output properties are inherited

    This is another feature that I really love about component Inheritance in Angular. In a simple sentence, whenever you have a custom @Input and @Output property, these properties get inherited.

    Component lifecycle is not inherited

    This part is the one that is not so obvious especially to people who have not extensively worked with OOP principles. For example, say you have ComponentA which implements one of Angular’s many lifecycle hooks like OnInit. If you create ComponentB and inherit ComponentA, the OnInit lifecycle from ComponentA won't fire until you explicitly call it even if you do have this OnInit lifecycle for ComponentB.

    Calling Super/Base Component Methods

    In order to have the ngOnInit() method from ComponentA fire, we need to use the super keyword and then call the method we need which in this case is ngOnInit. The super keyword refers to the instance of the component that is being inherited from which in this case will be ComponentA.

    0 讨论(0)
  • 2020-12-02 05:02

    Angular 2 version 2.3 was just released, and it includes native component inheritance. It looks like you can inherit and override whatever you want, except for templates and styles. Some references:

    • New features in Angular 2.3

    • Component Inheritance in Angular 2

    • A Plunkr demonstrating component inheritance

    0 讨论(0)
  • 2020-12-02 05:03

    If anyone is looking for an updated solution, Fernando's answer is pretty much perfect. Except that ComponentMetadata has been deprecated. Using Component instead worked for me.

    The full Custom Decorator CustomDecorator.ts file looks like this:

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

    Then import it in to your new component sub-component.component.ts file and use @CustomComponent instead of @Component like this:

    import { CustomComponent } from './CustomDecorator';
    import { AbstractComponent } from 'path/to/file';
    
    ...
    
    @CustomComponent({
      selector: 'subcomponent'
    })
    export class SubComponent extends AbstractComponent {
    
      constructor() {
        super();
      }
    
      // Add new logic here!
    }
    
    0 讨论(0)
提交回复
热议问题