Rendering dynamic components by selector name in Ivy

Post Editor

Rendering Components with component selector name along with lazy module loading. The approach that works with Angular Ivy & AOT compilation.

5 min read

Rendering dynamic components by selector name in Ivy

Rendering Components with component selector name along with lazy module loading. The approach that works with Angular Ivy & AOT compilation.

5 min read
5 min read

Angular gives us the mechanism to render components dynamically through View Container using ComponentFactory. To do this, we need to know the Component Type at the compile time.

The most dynamic component rendering mechanism would be the one where we don't know what component will be rendered at the compile time. This article talks about rendering an Angular component based on its selector, which is available only at run-time in the browser.

When do we need thisLink to this section

Generally this capability is helpful when we define what components to render at run-time based on some events or as per user actions. There could be multiple use-cases:

  1. Components to be rendered are known through some external source like a metadata JSON or an API response.
  2. Micro-front ends based on iframe and interacting through inter-iframe communication (JSON format) that needs to trigger component rendering.
  3. Any other cases where the component that would be rendered is not known upfront.

Rendering components using component selector and module pathLink to this section

To be able to render components by specifying the component selector, we need a way to get access to ComponentFactory of these components on-demand.

Getting component factory by component selectorLink to this section

Before Ivy (version 8), Angular had a mechanism to define entryComponents in modules. Adding the component to the entryComponents array would make the factories for these dynamic components available at runtime. It was needed to make sure TreeShaking does not remove these components from the final production bundle of the module.

Angular doesn't have a documented way of getting access to factories but a lot of people have used undocumented API like this to get it working:

const module: NgModuleFactory<unknown> = loadMyModule(); const factoryResolver = module.componentFactoryResolver; factoryResolver['_factories'].forEach(componentFactory => { // componentFactory.selector });
Getting Component Factories from ComponentFactoryResolver in Angular 8

As this was never officially supported or documented by Angular team, this stopped working with Angular 9 and above and people are left to find alternatives to keep supporting this. There is an open request on Angular project to support this, but they don't seem to be wanting to cater to this use case officially.

Dynamic Components by selector with IvyLink to this section

The suggested approach works by providing an interface similar to entryComponents through a custom field at the module level:

@NgModule({ imports: [CommonModule], declarations: [Dynamic1Component] }) export class Child1Module extends BaseModule { dynamicComponents = [Dynamic1Component]; constructor(componentFactoryResolver: ComponentFactoryResolver) { super(componentFactoryResolver); } }
Specifying dynamic components similar to entryComponents (Angular 8)

We need some application-level infrastructure to be able to define dynamic components then lazy load and render them when needed. Here's what we need to do:

  1. Create a base class BaseModule that each module exposing dynamic components needs to extends from
  2. Add all the components that need to be exposed to be available via component selectors as an array in dynamicComponents field
  3. Provide ComponentFactoryResolver to the base class

As we have the references to components via dynamicComponents field, these become part of chunks that are built and are not removed as part of TreeShaking.

We also have type safety here to make sure if a module is extending BaseModule, then dynamicComponents and provide ComponentFactoryResolver are also defined.

What all is BaseModule doing?Link to this section

Since we need ComponentFactory for rendering a component at runtime, we create a map of component selectors and their corresponding ComponentFactory instances for all components specified in the dynamicComponents field. This is done per module level through inheritance:

export abstract class BaseModule { private selectorToFactoryMap: { [key: string]: ComponentFactory<any> } = null; protected abstract dynamicComponents: Type<any>[]; // similar to entryComponents constructor(protected componentFactoryResolver: ComponentFactoryResolver) { } public getComponentFactory(selector: string): ComponentFactory<any> { if (!this.selectorToFactoryMap) { // lazy initialisation this.populateRegistry(); } return this.selectorToFactoryMap[selector]; } private populateRegistry() { this.selectorToFactoryMap = {}; if ( Array.isArray(this.dynamicComponents) && this.dynamicComponents.length > 0 ) { this.dynamicComponents.forEach(compType => { const componentFactory: ComponentFactory< any > = this.componentFactoryResolver.resolveComponentFactory(compType); this.selectorToFactoryMap[componentFactory.selector] = componentFactory; }); } } }
BaseModule: Storing map of selectors and corresponding ComponentFactories

Here, we have a public function getComponentFactory that takes the component selector and returns a ComponentFactory for that component.

We have a hash map selectorToFactoryMap that holds module level selector to factory mapping. We make sure this map is lazily initialized when first requested for a dynamic component in this module.

ConsumptionLink to this section

We will create a helper service that we will be injected and used where we need to get components for dynamic rendering:

export class DynamicComponentService { constructor(private injector: Injector) {} getComponentBySelector( componentSelector: string, moduleLoaderFunction: () => Promise<any> ): Promise<ComponentRef<unknown>> { return this.getModuleFactory(moduleLoaderFunction).then(moduleFactory => { const module = moduleFactory.create(this.injector); if (module.instance instanceof BaseModule) { const compFactory: ComponentFactory< any > = module.instance.getComponentFactory(componentSelector); return compFactory.create(module.injector, [], null, module); } else { throw new Error('Module should extend BaseModule to use "string" based component selector'); } }); } async getModuleFactory( moduleLoaderFunction: () => Promise<NgModuleFactory<any>> ) { const ngModuleOrNgModuleFactory = await moduleLoaderFunction(); let moduleFactory; if (ngModuleOrNgModuleFactory instanceof NgModuleFactory) { // AOT moduleFactory = ngModuleOrNgModuleFactory; } else { // JIT moduleFactory = await this.injector .get(Compiler) .compileModuleAsync(ngModuleOrNgModuleFactory); } return moduleFactory; } }
Helper service for getting component's ComponentRef by its selector

The helper service exposes a utility function getComponentBySelector that takes component selector and dynamic import statement for the module. The service works in the following manner:

  1. Loads the module and checks if it extends BaseModule or not
  2. Creates an instance of the module and calls the method getComponentFactory exposed by BaseModule to get the component factory. The baseModule now initialises the map and populates all selectors and ComponentFactory mapping for the module

We will use the helper service in the following manner:

  1. We have a placeholder container div in our template where we need a dynamic component. We get ViewContainerRef for the same.
  2. We request DynamicComponentService for the ComponentRef of a component with selector 'app-dynamic1' that is part of child1.module file.
  3. We insert the ComponentRef inside our container div

Here's the code demonstrating the approach:

@ViewChild("container", { read: ViewContainerRef, static: true }) container: ViewContainerRef; constructor(private componentService: DynamicComponentService) {} addDynamicComponent() { this.componentService .getComponentBySelector("app-dynamic1", () => import("./child1/child1.module").then(m => m.Child1Module) ) .then(componentRef => { this.container.insert(componentRef.hostView); }); }

Inputs to the componentLink to this section

One problem with dynamic rendering in Angular is that component inputs are not automatically bound to the fields of the parent component. To solve that problem and set up automatic inputs propagation, we will add some custom binding functionality to a wrapping component.

The component will use the DynamicComponentService we created above and set the inputs to the component instance. It also makes sure any updates to inputs are updated in the instance or if the selector is changed, a new component is created:

export interface DynamicComponentInputs { [k: string]: any; }; @Component({ selector: 'app-dynamic-selector', template: ` <ng-container #componentContainer></ng-container> ` }) export class DynamicSelectorComponent implements OnDestroy, OnChanges { @ViewChild('componentContainer', { read: ViewContainerRef, static: true }) container: ViewContainerRef; @Input() componentSelector: string; @Input() moduleLoaderFunction; @Input() inputs: DynamicComponentInputs; public component: ComponentRef<any>; constructor(private componentService: DynamicComponentService) { } async ngOnChanges(changes: SimpleChanges) { if (changes.componentSelector) { await this.renderComponentInstance(); this.setComponentInputs(); } else if (changes.inputs) { this.setComponentInputs(); } } ngOnDestroy() { this.destroyComponentInstance(); } private async renderComponentInstance() { this.destroyComponentInstance(); this.component = await this.componentService.getComponentBySelector(this.componentSelector, this.moduleLoaderFunction); this.container.insert(this.component.hostView); } private setComponentInputs() { if (this.component && this.component.instance && this.inputs) { Object.keys(this.inputs).forEach(p => (this.component.instance[p] = this.inputs[p])); } } private destroyComponentInstance() { if (this.component) { this.component.destroy(); this.component = null; } } }

LimitationsLink to this section

We should also keep in mind that dynamism brings in some limitations.

1. TypeSafety
As the component to be created is dynamically decided, it is not type safe while consuming it.

2. Errors at Runtime
It will be difficult to find errors at compile time. If the component is not exposed as a dynamic component by the module, there will be exceptions seen at runtime. Though this was the case even when using entryComponents.

ConclusionLink to this section

The approach shared in the article is useful in cases where the requirement is to be very dynamic components and no criteria is available at build time to decide which component should be rendered where.

I have personally used this approach in projects where we have iframe based integrations and dialogs render components dynamically by getting component the selector name by using Window.postMessage API.

Try itLink to this section

The sample Angular application that render dynamic components via selector is available here:

CreditsLink to this section

Thanks to Suresh Nagar and Shrinivas Parashar with whom I collaborated to come up with this solution.

Discuss with community


About the author


Front End Experience Developer with 6+ years of working experience. Passionate about creating robust & performant user interfaces. Currently working on Large scale Network Visualizations.


About the author

Tarang Khandelwal

Front End Experience Developer with 6+ years of working experience. Passionate about creating robust & performant user interfaces. Currently working on Large scale Network Visualizations.

About the author


Front End Experience Developer with 6+ years of working experience. Passionate about creating robust & performant user interfaces. Currently working on Large scale Network Visualizations.


Featured articles