Dynamically load components in Angular 13

Reading Time: 3 minutes

Loading

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<Component>. 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 <ng-container #viewContainerRef> 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>

We use ViewChild('viewContainerRef') to obtain the ViewContainerRef. Moreover, we declare ComponentRef<FoodCardComponent> 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>[] = []

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

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

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<C> 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[][];
}): ComponentRef<C>;

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.

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.

Resources:

  1. Github Repository: https://github.com/railsstudent/ng-spanish-menu
  2. ViewContainerRef: https://angular.io/api/core/ViewContainerRef#createcomponent