IvanS95
IvanS95

Reputation: 5732

How to load dynamic components based on a property from object?

I'm trying to build a list of cards which may contain different components; So for example I have the following array of objects:

{
   title: 'Title',
   descrption: 'Description',
   template: 'table',
},
{
  title: 'Title',
  descrption: 'Description',
  template: 'chart',
}

I get this array as a response from a service, then I need to match each of thos objects to a component based on the template property, so for example, the first item should match to the TableComponent and the second one to the ChartComponent;

I'm trying to follow the Angular Docs regarding Dynamic Component Loading, but I'm not sure how tell the method how to match each object in the array to a specific component.

In my parent component I have made an anchor point where the components should load with a directive:

<ng-template appCheckpointHost></ng-template>

And I'm trying to use the ComponentFactoryResolver as it shows in the example.

loadComponent() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ChartCheckpointComponent);
    const viewContainerRef = this.checkHost.viewContainerRef;
  }

The example shows a scenario in which the "service" runs every three seconds, gets a random item, and shows it; but what I'm trying to do instead is to fetch all the items when the parent component loads, and render each item with its respective component.

Any ideas to get this to work?

Upvotes: 2

Views: 2660

Answers (2)

yurzui
yurzui

Reputation: 214047

You can create a dictionary like:

const nameToComponentMap = {
  table: TableComponent,
  chart: ChartComponent 
};

And then just use this dictionary to determine which component should be rendered depending on the template property of particular item in your items array:

const componentTypeToRender = nameToComponentMap[item.template];
this.componentFactoryResolver.resolveComponentFactory(componentTypeToRender);

Upvotes: 2

Tony
Tony

Reputation: 20092

You can view my blog here

First I will need to create a directive to reference to our template instance in view

import { Directive, ViewContainerRef } from "@angular/core";

@Directive({
    selector: "[dynamic-ref]"
})
export class DynamicDirective {
    constructor(public viewContainerRef: ViewContainerRef) {}
}

Then we simply put the directive inside the view like this

 <ng-template dynamic-ref></ng-template>

We put the directive dynamic-ref to ng-content so that we can let Angular know where the component will be render

Next I will create a service to generate the component and destroy it

import {
    ComponentFactoryResolver,
    Injectable,
    ComponentRef
} from "@angular/core";
@Injectable()
export class ComponentFactoryService {
    private componentRef: ComponentRef<any>;
    constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

    createComponent(
        componentInstance: any,
        viewContainer: any
    ): ComponentRef<any> {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
            componentInstance
        );
        const viewContainerRef = viewContainer.viewContainerRef;
        viewContainerRef.clear();
        this.componentRef = viewContainerRef.createComponent(componentFactory);
        return this.componentRef;
    }

    destroyComponent() {
        if (this.componentRef) {
            this.componentRef.destroy();
        }
    }
}

Finally in our component we can call the service like this

@ViewChild(DynamicDirective) dynamic: DynamicDirective;

constructor(
        private componentFactoryService: ComponentFactoryService
    ) {

    }

ngOnInit(){
       const dynamiCreateComponent = this.componentFactoryService.createComponent(TestComponent, this.dynamic);
       (<TestComponent>dynamiCreateComponent.instance).data = 1;
       (<TestComponent>dynamiCreateComponent.instance).eventOutput.subscribe(x => console.log(x));
}

ngOnDestroy(){
  this.componentFactoryService.destroyComponent();
}

/////////////////////////////////
export class TestComponent {
  @Input() data;
  @Output() eventOutput: EventEmitter<any> = new EventEmitter<any>();

  onBtnClick() {
    this.eventOutput.emit("Button is click");
  }      
}

Upvotes: 1

Related Questions