DEV Community

Connie Leung
Connie Leung

Posted on

Dynamically load components in Angular 13

Introduction

Dynamically load components has simplified in Angular 13. Angular team deprecated ViewContainerRef in Angular 13 and we can load components to attached view container without it now.

Let’s recap how we load components in Angular 12. We inject ComponentFactoryResolver to map component to ComponentFactory. Then, we pass the ComponentFactory to ViewContainerRef to create an instance of ComponentRef. Through the ComponentRef, we can access the component and initialize its values to render the component in the attached view container.

In this post, we see the codes to dynamically load components in Angular 12 and Angular 13 and highlight the differences between the code snippets.

Let's go

Dynamically load components in Angular 12 and 13

The use case of this post is to load FoodCardComponent in FoodShellComponent. In the html template, there is a with viewContainerRef.

// food-shell.component.ts  inline template

<div class="p-[1.125rem]">
  <section class="flex flex-wrap items-stretch p-2 mb-1">
     <ng-container #viewContainerRef></ng-container>
  </section>
</div>
Enter fullscreen mode Exit fullscreen mode

We use ViewChild('viewContainerRef') to obtain the ViewContainerRef. Moreover, we declare ComponentRef array to release the memory of FoodCardComponent in ngOnDestroy to avoid memory leaks.

@ViewChild('viewContainerRef', { read: ViewContainerRef, static: true })
public orderedViewContainer: ViewContainerRef

public componentRefs: ComponentRef<FoodCardComponent>[] = []
Enter fullscreen mode Exit fullscreen mode

First, we show the Angular 12 codes that attaches FoodCardComponent to #viewContainerRef.

constructor(
   private componentFactoryResolver: ComponentFactoryResolver,
   private foodService: FoodService,
   private cdr: ChangeDetectorRef,
) {}

public async addDynamicFoodChoice(choice: OrderedFoodChoice): Promise<void> {
    const { FoodCardComponent } = await import('../food-card/food-card.component')
    const resolvedComponent = this.componentFactoryResolver
.resolveComponentFactory(FoodCardComponent)
    const componentRef = this.orderedViewContainer
.createComponent(resolvedComponent)
    const { total } = this.foodService.calculateTotal([choice])

    componentRef.instance.ordered = {
      ...choice,
    }

    componentRef.instance.total = total
    this.componentRefs.push(componentRef)

    this.orderedFood = [...this.orderedFood, choice]
    this.cdr.detectChanges()
}
Enter fullscreen mode Exit fullscreen mode

Next, we show the Angular 13 codes that achieve the same result.

constructor(private foodService: FoodService, private cdr: ChangeDetectorRef) {}

public async addDynamicFoodChoice(choice: OrderedFoodChoice): Promise<void> {
    const { FoodCardComponent } = await import('../food-card/food-card.component')
    const componentRef = this.orderedViewContainer
.createComponent(FoodCardComponent)
    const { total } = this.foodService.calculateTotal([choice])

    componentRef.instance.ordered = {
      ...choice,
    }

    componentRef.instance.total = total
    this.componentRefs.push(componentRef)

    this.orderedFood = [...this.orderedFood, choice]
    this.cdr.detectChanges()
 }
Enter fullscreen mode Exit fullscreen mode

So far so good

Compare dynamically load components between Angular 12 and 13

Lastly, we compare the new changes of load components between the two version.

The first change is the constructor does not require to inject ComponentFactoryResolver. The second change is we pass the type of the component to ViewContainerRef.createComponent() to obtain an instance of ComponentRef.

Finally, we examine the API of ViewContainerRef where createComponent is defined:

The overloaded version of createComponent accepts Type as the first parameter. The second parameter is an object parameter encapsulating index, injector, ngModuleRef and prjectableNodes.

abstract createComponent<C>(componentType: Type<C>, options?: {
   index?: number;
   injector?: Injector;
   ngModuleRef?: NgModuleRef<unknown>;
   projectableNodes?: Node[][];
}): Com
Enter fullscreen mode Exit fullscreen mode

Moreover, the signature of createComponent that accepts ComponentFactory is deprecated. If applications require to create a dynamic component, they should pass the component type to createComponent directly.

deprecated createComponent

Final thoughts

Create dynamic components has updated in Angular 13 and ComponentFactoryResolver is deprecated since the release. When developers create dynamic components in Angular 13, they should use the new signature of createComponent and pass the component type to the method.

If existing applications are using ComponentFactoryResolver, they will have to remove all occurrences of ComponentFactoryResolver and update all arguments of ComponentFactoryResolver with arguments of component type.

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.

We made it

Resources:

Top comments (2)

Collapse
 
_ac4mm profile image
ac4mm

Thank you so much for the comparison, i appreciated it!

Collapse
 
alexandis profile image
alexandis • Edited

How to create modal dialog dynamically if no ViewContainerRef available, i.e. I cannot use createComponent method and feed the result to modal.open?
This is happening because I need to pass a click handler (which contains modal.open) inside injected service method. This method is called from main app component - which does not have any ViewChild references available yet.