

How Angular Resolves Dynamic Components
source link: https://www.tuicool.com/articles/hit/Z3Abay3
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

What really happens when we use the resolveComponentFactory
method in Angular? let’s peek under the hood.

Introduction
Dynamic components are one of the core concepts introduced in Angular, they enable developers to dynamically render a component’s view during runtime. This article explains how the ComponentFactoryResolver
class resolves a ComponentFactory
.
Why should we know this? Knowing how Angular works its nuts and bolts, will go a long way to understand it better and also you’ll be better off when building applications and dealing with bug-related issues.
Angular Components and Factories
On execution, Angular isn’t made up of Components. Everything is compiled down to JavaScript before execution. Components, Modules, Directives are all turned into factories. In other words, Angular is made up of factories (ComponentFactory, NgModuleFactory).
So before begin executed, this:
@Component({ selector: 'app-root', template: ` <p *ngFor='let fruit of fruits'>{{fruit}}</p> `, styles: [] }) export class AppComponent { title = 'app'; fruits = ['orange','apple'] constructor(){} changeTitle() { this.title = 'Angular app' } }
Would be compiled to this:
function View_AppComponent_0(_l) { return i0.ɵvid(0, [ (_l()(), i0.ɵted(-1, null, ["\n "])), (_l()(), i0.ɵeld(1, 0, null, null, 4, "div", [], null, null, null, null, null)), (_l()(), i0.ɵted(-1, null, ["\n "])), (_l()(), i0.ɵeld(3, 0, null, null, 1, "p", [], null, null, null, null, null)), (_l()(), i0.ɵted(4, null, ["\n ", " works!!\n "])), (_l()(), i0.ɵted(-1, null, ["\n "])), (_l()(), i0.ɵted(-1, null, [" \n "])) ], null, function(_ck, _v) { var _co = _v.component; var currVal_0 = _co.title; _ck(_v, 4, 0, currVal_0); }); } exports.View_AppComponent_0 = View_AppComponent_0; function View_AppComponent_Host_0(_l) { return i0.ɵvid(0, [ (_l()(), i0.ɵeld(0, 0, null, null, 1, "app-root", [], null, null, null, View_AppComponent_0, RenderType_AppComponent)), i0.ɵdid(1, 49152, null, 0, i2.AppComponent, [], null, null) ], null, null); } exports.View_AppComponent_Host_0 = View_AppComponent_Host_0; var AppComponentNgFactory = i0.ɵccf("app-root", i2.AppComponent, View_AppComponent_Host_0, {}, {}, []); exports.AppComponentNgFactory = AppComponentNgFactory;
In this article we will look in-depth how Angular resolves a dynamic component’s factory using resolveComponentFactory
method:
const factory = r.resolveComponentFactory(AComponent); factory.create(injector);
Example
Let’s say we have a ChildComponent
:
@Component({ selector: 'child', template: ` <h1>Child Component</h1> `, }) export class ChildComponent { }
And a ParentComponent
that dynamically appends the ChildComponent's view onto its own:
@Component({ selector: 'app-root', template: ` <template #parent></template> <button (click)="createChild()">Create Child</button> `, }) export class ParentComponent { @ViewChild('parent', { read: ViewContainerRef }) container; constructor(private resolver: ComponentFactoryResolver) {} createChild() { this.container.clear(); const factory: ComponentFactory = this.resolver.resolveComponentFactory(ChildComponent); this.componentRef: ComponentRef = this.container.createComponent(factory); } }
Great! The template element is where the ChildComponent will be appended.
The most important things we have to note here is the injection of the ComponentFactoryResolver
and the resolution of the ChildComponent
in the createChild
method. When the Create Child
button is clicked, the createChild
method is run which first clears the template element, then get the Component factory of the ChildComponent
and goes forward to create the ChildComponent
's view on the DOM.
The question here is: What is ComponentFactoryResolver? and how does its method resolveComponentFactory
get the factory of a component?
What is ComponentFactoryResolver
?
ComponentFactoryResolver
is a class in Angular that stores the factories of components in a key-value store.
--------------- key | value ---------------- [TodoComponent => TodoComponentNgFatory] [AboutComponent => AboutComponentNgFactory]
It takes the array of ComponentFactory
type, a parent ComponentFactoryResolver
and a NgModuleRef
as arguments on instantiation.
export class CodegenComponentFactoryResolver implements ComponentFactoryResolver { constructor( factories: ComponentFactory<any>[], private _parent: ComponentFactoryResolver, private _ngModule: NgModuleRef<any>) {...} }
During the construction of its object, its loops through the factories
array and stores each ComponentFactory in a Map with the componentType as its key and the factory as the value:
private _factories = new Map<any, ComponentFactory<any>>(); constructor( factories: ComponentFactory<any>[], private _parent: ComponentFactoryResolver, private _ngModule: NgModuleRef<any>) { for (let i = 0; i < factories.length; i++) { const factory = factories[i]; this._factories.set(factory.componentType, factory); } }
This is more like an object. It uses key-value mechanism to store and retrieve values:
var store = { //key : value one: 1, two: 2 }
// To get the value `1`, we reference with its key `one` console.log(store.one) // 1
// Value can be stored with its key `three => 3`
store['three'] = 3 console.log(store.three) // 3
How does resolveComponentFactory
get the factory of a component?
Simple, as it stores it in a Map with a key-value pair. All it has to do to get the factory of a component is to reference it by its key.
resolveComponentFactory<T>(component: {new (...args: any[]): T}): ComponentFactory<T> { let factory = this._factories.get(component); if (!factory && this._parent) { factory = this._parent.resolveComponentFactory(component); } if (!factory) { throw noComponentFactoryError(component); } return new ComponentFactoryBoundToModule(factory, this._ngModule); }
The get
method is used in a Map to get the value by its key. So, here the get
method is used to retrieve the ComponentFactory by its key ( component
).
The next question is how does ComponentFactoryResolver
receive the ComponentFactory
array?
Earlier at our article introduction, we saw that components are compiled down to JS code called factories
before being executed. Components are compiled to generate ComponentFactory on execution, and Modules generate NgModuleFactory.
createNgModuleFactory function is used to generate NgModuleFactory.
The AppModule
of our app:
@NgModule({ declarations: [ AppComponent, ChildComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
Its factory will look like this:
var AppModuleNgFactory = i0.ɵcmf(i1.AppModule, [i2.AppComponent], function(_l) { return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [ [8, [i3.AppComponentNgFactory]], [3, i0.ComponentFactoryResolver], i0.NgModuleRef ]), i0.ɵmpd(5120, i0.LOCALE_ID, i0.ɵm, [ [3, i0.LOCALE_ID] ]), ... i0.ɵmpd(131584, i0.ApplicationRef, i0.ApplicationRef, [i0.NgZone, i0.ɵConsole, i0.Injector, i0.ErrorHandler, i0.ComponentFactoryResolver, i0.ApplicationInitStatus]), i0.ɵmpd(512, i0.ApplicationModule, i0.ApplicationModule, [i0.ApplicationRef]), i0.ɵmpd(512, i5.BrowserModule, i5.BrowserModule, [ [3, i5.BrowserModule] ]), i0.ɵmpd(512, i6.ɵba, i6.ɵba, []), i0.ɵmpd(512, i6.FormsModule, i6.FormsModule, []), i0.ɵmpd(512, i1.AppModule, i1.AppModule, [])]); });
Notice the obscure method names starting theta ɵ
. This is employed by Angular to reduce bundle size on minification. These method reference the actual method implementation, ɵcmf
calls c reateNg M odule F actory , ɵmpd
=> m odule P rovider D ef , ɵmod
=> mo dule D ef .
We see that c reateNg M odule F actory takes a Module class, bootstrap components, and a module definition factory as arguments.
export function createNgModuleFactory( ngModuleType: Type<any>, bootstrapComponents: Type<any>[], defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> { return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory); }
We will be more interested in the module definition factory. NgModuleFactroyDefinition provides the context used for component resolution and dependency injection.
Looking into it, we see that it calls moduleDef function which is passed an array of moduleProviderDef calls.
moduleProviderDef
is a function that defines how a class will be stored and retrieved by an Injector.
export function moduleProvideDef( flags: NodeFlags, token: any, value: any, deps: ([DepFlags, any] | any)[]): NgModuleProviderDef { ... return { // will bet set by the module definition index: -1, deps: depDefs, flags, token, value }; }
Here the flags
parameter, denotes how the value of a class/provider should be resolved or instantiated:
export const enum NodeFlags { ... TypeValueProvider = 1 << 8, TypeClassProvider = 1 << 9, TypeFactoryProvider = 1 << 10, TypeUseExistingProvider = 1 << 11 ... }
Next, the token: any
parameter is the key by which the value of the class/provider is retrieved by the Injector. value
is the actual value of the token
. deps: ([DepFlags, any] | any)[]
is the dependencies used to construct the instance of the class.
Now, moduleProviderDef
parses all this into an object and returns it. moduleDef receives an array of it and parses it in an array of providers
.
export function moduleDef(providers: NgModuleProviderDef[]): NgModuleDefinition { ... return { // Will be filled later... factory: null, providersByKey, providers }; }
When module factory is created through createNgModuleFactory function call, a series of calls are made which eventually lead to the resolution of the providers:
export function initNgModule(data: NgModuleData) { const def = data._def; const providers = data._providers = new Array(def.providers.length); for (let i = 0; i < def.providers.length; i++) { const provDef = def.providers[i]; if (!(provDef.flags & NodeFlags.LazyProvider)) { providers[i] = _createProviderInstance(data, provDef); } } }
Here the providers array generated by moduleDef function is looped through and instance of each provider is created and stored in _providers
property of the NgModuleDef_
:
function _createProviderInstance(ngModule: NgModuleData, providerDef: NgModuleProviderDef): any { let injectable: any; switch (providerDef.flags & NodeFlags.Types) { case NodeFlags.TypeClassProvider: injectable = _createClass(ngModule, providerDef.value, providerDef.deps); break; case NodeFlags.TypeFactoryProvider: injectable = _callFactory(ngModule, providerDef.value, providerDef.deps); break; case NodeFlags.TypeUseExistingProvider: injectable = resolveNgModuleDep(ngModule, providerDef.deps[0]); break; case NodeFlags.TypeValueProvider: injectable = providerDef.value; break; } return injectable === undefined ? UNDEFINED_VALUE : injectable; }
We went through all of this to deduce how ComponentFactoryResolver gets its ComponentFactory array. Next, let’s look at our module factory definition.
return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [ [8, [i3.AppComponentNgFactory]], [3, i0.ComponentFactoryResolver], i0.NgModuleRef ])
Looking at how ComponenetFactoryResolver is configured, we see that 512
, which denotes TypeClassProvider
in NodeFlags object, is passed in. This tells Angular we are configuring the provider from a class.
Next, i0.ComponentFactoryResolver is passed as a token, and i0.ɵCodegenComponentFactoryResolver as the value. The last param is the dependencies of ComponentFactoryResolver.
Remember, when we looked into ComponentFactoryResolver definition earlier, we saw it takes factories: ComponentFactory
, private _parent: ComponentFactoryResolver
, private _ngModule: NgModuleRef
as parameters in its constructor.
So, here ComponentFactoryResolver instance will be instantiated like this:
const resolver = new ComponentFactoryResolver([i3.AppComponentNgFactory], i3.ComponentFactoryResolver, i0.NgModuleRef)
As you see, an array of ComponentFactory is passed to it. So, during the construction of its object, it loops through the array and stores the key-value pair of each ComponentFactory in the _factories
Map property.
So when we do this:
... createChild() { ... const factory: ComponentFactory = this.resolver.resolveComponentFactory(ChildComponent); ... } ...
ComponentFactoryResolver
loops through _factories Map and retrieves the ComponentFactory from the key ChildComponent
supplied.
If we run our app like this and click on the Create Child
button, noComponentFactoryError error will be thrown.
Why? Because the ChildComponent was not passed to ComponentFactoryResolver in the ComponentFactory array param, so it wasn't present in the _factories
key-value store.
This is the reason tutorials dealing with Dynamic Angular Components always refer to add all your dynamically created components in the entryComponents
property of your root NgModule (AppModule).
@NgModule({ declarations: [ AppComponent, ChildComponent ], imports: [ BrowserModule, FormsModule ], providers: [], entryComponents:[AppComponent,ChildComponent], bootstrap: [AppComponent] }) export class AppModule { }
So, Angular Compiler “know” they are to be added to ComponentResolverFactory dependencies during compilation.
var AppModuleNgFactory = i0.ɵcmf(i1.AppModule, [i2.AppComponent], function(_l) { return i0.ɵmod([i0.ɵmpd(512, i0.ComponentFactoryResolver, i0.ɵCodegenComponentFactoryResolver, [ [8, [i3.AppComponentNgFactory, i3.ChildComponentNgFactory]], [3, i0.ComponentFactoryResolver], i0.NgModuleRef ]), ... ]); });
Now, when ComponentFactoryResolver dependency is configured, the ChildComponentFactory will be present:
const resolver = new ComponentFactoryResolver([i3.AppComponentNgFactory,i3.ChildComponentNgFactory], i3.ComponentFactoryResolver, i0.NgModuleRef)
Conclusion
We have seen how Angular resolves dynamic components. The moduleProviderDef
function is used to configure a provider for the Angular dependency injection framework.
This is where ComponentFactory
-s of dynamic components are passed to ComponentFactoryResolver
class for resolution of a component's factory during runtime.
That’s it, now we know what happens under the hood when we use the resolveComponentFactory
method.
Thanks!!!
Recommend
-
31
Western Digital has released a hotfix to resolve a serious authentication bypass vulnerability in the My Cloud product line which permits attackers to hijack and take full control of a vulnerable device. The tec...
-
6
True Wireless Earbuds under Rs 3,000: Skullcandy Spoke, OnePlus Buds Z Review SnapshotsWe have two affordable TWS earbuds under Rs 3,000 from Skullcandy and OnePlus that offer excellent sound quality i...
-
3
Who Resolves Conflict in Agile and How If not resolved at a...
-
12
Mar 15, 2021 How C++ Resolves a Function Call C is a simple language. You’re only allowed to have one function with each name. C++, on the other hand, gives you much more flexibility: You...
-
8
Innovative Chip Resolves Quantum Headache – Paves Road to Supercomputer of the Future By University of Copenhagen October 31, 2021 Size comparison of qubits The illustration shows the size difference between spi...
-
5
The installation of printers should no longer fail Microsoft says it has managed to resolve another Windows 11 problem, this time causing the installation of printers to fail when attempted over net...
-
5
New cumulative update coming with lots of fixes The most recent Windows 10 cumulative updates released as part of the December 2021 Patch Tuesday cycle come with lots of fixes, including one aimed a...
-
3
So the battery charge level should now be shown correctly If your device has already been updated to Windows 11 and the battery level sometimes shows a percentage that’s well above 100%, well, you’r...
-
6
Including a connection error to Microsoft.com Mozilla is releasing a new Firefox version whose purpose is to correct several critical bugs impacting Windows users. The new versi...
-
5
Introduction In Angular, ngComponentOutlet is the simple way to render components dynamically. The other option is to render dynamic components using ViewContainerRef class. ViewContainerRef...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK