In Angular, the ComponentFactoryResolve
class used to be used to create dynamically load components before Angular 13. With the release of Angular 13, the ComponentFactoryResolver
class is marked as deprecated when you see its code class comments:
[lwptoc]
....
* @deprecated Angular no longer requires Component factories. Please use other APIs where
* Component class can be used directly.
*/
export declare abstract class ComponentFactoryResolver {
static NULL: ComponentFactoryResolver;
/**
* Retrieves the factory object that creates a component of the given type.
* @param component The component type.
*/
abstract resolveComponentFactory<T>(component: Type<T>): ComponentFactory<T>;
}
....
Moreover, even if you use ComponentFactoryResolver in your component, you will see a message saying:
'ComponentFactoryResolver' is deprecated.ts(6385)
index.d.ts(1395, 4): The declaration was marked as deprecated here.
(alias) class ComponentFactoryResolver
import ComponentFactoryResolver
A simple registry that maps Components to generated ComponentFactory classes that can be used to create instances of components. Use to obtain the factory for a given component type, then use the factory's create() method to create a component of that type.
Note: since v13, dynamic component creation via ViewContainerRef.createComponent does not require resolving component factory: component class can be used directly.
What are Dynamic Components in Angular?
Dynamic components in Angular are components that are created and added to the DOM during runtime, rather than defined in the template HTML statically. They provide a flexible way to create and manage components based on user interactions, application logic, or changing requirements. The Dynamic components are rendered at the time when they are appended to the DOM.
Dynamic components can be useful in various scenarios, such as:
- Loading content based on user actions, like clicking a button or selecting an option.
- Rendering a set of components based on data fetched from an API or database.
- Creating reusable components that can be instantiated with different configurations and data.
How we used Dynamic Components before Angular 13?
Let’s have a look at a simple example of Dynamic components implementation. In our examples, we will have two components named ProfileA and ProfileB components, these components will be appended dynamically and rendered on the click of the button event.
Also, the dynamic component will be appended dynamically without using any template variable element, @ViewChild or directive usage.
// app.component.ts
import {
Component,
ComponentFactoryResolver,
Injector,
ApplicationRef,
ElementRef,
} from '@angular/core';
import { ProfileAComponent } from './profile-a/profile-a.component';
import { ProfileBComponent } from './profile-b/profile-b.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private appRef: ApplicationRef,
private el: ElementRef
) {}
loadProfileA() {
this.loadComponent(ProfileAComponent);
}
loadProfileB() {
this.loadComponent(ProfileBComponent);
}
private loadComponent(component: any) {
const factory =
this.componentFactoryResolver.resolveComponentFactory(component);
const componentRef = factory.create(this.injector);
this.appRef.attachView(componentRef.hostView);
this.el.nativeElement.appendChild(componentRef.location.nativeElement);
componentRef.onDestroy(() => {
this.appRef.detachView(componentRef.hostView);
});
}
}
In the HTML template, we have two buttons:
<!-- app.component.html -->
<button (click)="loadProfileA()">Button A</button>
<button (click)="loadProfileB()">Button B</button>
This is how our app will work:
How to Resolve ‘ComponentFactoryResolver’ is deprecated Issue?
Our application is working fine, but soon the ‘ComponentFactoryResolver’ class will be deprecated. So we need to find out how we can make the transition with minimal changes.
Believe me, there will be very small and minimal change 🙂
The solution is also in problem itself, just notice the message you are seeing in the notification window. it is saying that:
Note: since v13, dynamic component creation via
ViewContainerRef.createComponent
does not require resolving component factory: component class can be used directly.
So we need to replace the ComponentFactoryResolver
usages with <span >ViewContainerRef</span>
Replace the loadComponent()
function with the following:
private loadComponent(component: any) {
const componentRef = this.viewContainerRef.createComponent(component);
this.appRef.attachView(componentRef.hostView);
this.el.nativeElement.appendChild(componentRef.location.nativeElement);
componentRef.onDestroy(() => {
this.appRef.detachView(componentRef.hostView);
});
}
Change #1 – Remove the following:
ComponentFactoryResolver,
Injector,
Import the ViewContainerRef
Change #2 – Update the construction injections:
Change #3 – Update loadComponent()
The complete app.component.ts class will look like this:
// app.component.ts
import {
Component,
ComponentFactoryResolver,
Injector,
ApplicationRef,
ElementRef,
ViewContainerRef,
} from '@angular/core';
import { ProfileAComponent } from './profile-a/profile-a.component';
import { ProfileBComponent } from './profile-b/profile-b.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private injector: Injector,
private appRef: ApplicationRef,
private el: ElementRef,
private viewContainerRef: ViewContainerRef
) {}
loadProfileA() {
this.loadComponent(ProfileAComponent);
}
loadProfileB() {
this.loadComponent(ProfileBComponent);
}
private loadComponent(component: any) {
const componentRef = this.viewContainerRef.createComponent(component);
this.appRef.attachView(componentRef.hostView);
this.el.nativeElement.appendChild(componentRef.location.nativeElement);
componentRef.onDestroy(() => {
this.appRef.detachView(componentRef.hostView);
});
}
}
Conclusion
Hope, you will find this transition process easy to follow. Even though there are some challenges you may face while using the ViewContainerRef as service dependency inside the factories. We will discuss that issue in the next post to understand its use cases, challenges and of course the solutions 🙂