Search code examples
angularpolymorphism

Angular 2 components polymorphism


I would like to implement a component acting like a container with dynamically added children components. The type and number of children components should be configured on server side.

So Models arrived from the server could look like this:

export class ModelBase {
    public data: any;
}

export class ModelA extends ModelBase {
    public dataA: any;
}

export class ModelB extends ModelBase {    
    public dataB: any
}

Simplified components could be like this:

@Component({
  selector: "component-base"
})
export class BaseComponent {
  @Input() model: ModelBase;
}

@Component({
  selector: "component-a",
  template: "<div>component-a</div>"
})
export class AComponent extends BaseComponent {
  @Input() model: ModelBase;
}

@Component({
  selector: "component-b",
  template: "<div>component-b</div>"
})
export class BComponent extends BaseComponent {
  @Input() model: ModelBase;
}

And here is the Application and how I would like to work with my components:

@Component({
        selector: 'app',
        template: `
          <div *ngFor="#model of models">
              <component-base [model]="model"></component-base>
          </div>
        `
})
export class App {
}

I want component-base will be replaced with the concrete implementation based on model type. For example, with component-a. Does workflow like this is possible to implement with Angular 2?


Solution

  • There might be a better API for this functionality coming: https://github.com/angular/angular/pull/11235.

    In the meantime, check out https://stackoverflow.com/a/36325468/5307109. You'll likely have to modify it so that you can pass data through it to your destination component and call a custom component hook on load.

    Using the wrapper described above, your ComponentBase will have a template of its own and inject each model's associated component into the wrapper.

    export class ModelA extends ModelBase {
      dataA: any;
      component: any = AComponent;
    }
    
    @Component({
      selector: "component-base",
      template: `
        <dcl-wrapper [type]="model.component" [init-data]="model"></dcl-wrapper>
      `
    })
    export class BaseComponent {
      @Input() model: ModelBase;
    }
    
    @Component({
      selector: "component-a",
      template: "<div>component-a</div>"
    })
    export class AComponent {
      @Input() model: ModelBase;
    
      /**
       * Custom hook you might create in DclWrapper.
       */
      onDclInit(model: ModelBase) : void {
        this.model = model;
      }
    
    }