2

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?

3
  • you can use ngSwitch(angular.io/api/common/NgSwitch) to render component based on template value Commented Aug 22, 2019 at 20:25
  • @ajaiJothi yeah was kinda trying to avoid ngSwitch; I don't know but it feels like a more efficient way to use Dynamic loading? Could be wrong though, don't really know the advantages/disadvantages of the two Commented Aug 22, 2019 at 20:27
  • 1
    you need to use switch statement either in the template or ts file (if you are using componentFactory) - since you need to pass the component reference to resolveComponentFactory. I dont see a way to locate component dynamically based on a string value Commented Aug 22, 2019 at 20:32

2 Answers 2

2

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);
Sign up to request clarification or add additional context in comments.

3 Comments

That does look great, and say for the multiple components since it is a large array I should just put the this.componentFactoryResolver.resolveComponentFactory(componentTypeToRender); inside a forEach loop? Sounds like the most obvious way to me
Yes, there is nothing hard here. Your can just loop over your array and pass dedicated type depending on item.template
Okay great; bad habit of mine to overcomplicate stuff, thank you very much, this does seem to take care of it pretty easily
1

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

1 Comment

How can I modify this to allow multiple different components to be rendered at the same time? By what I'm seeing I imagine it would have to be something regarding the code inside ngOnInit() maybe running those lines for each element in my array?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.