Skip to content

Instantly share code, notes, and snippets.

@philipszdavido
Last active September 1, 2020 14:14
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save philipszdavido/47bb7ab62e665de7b5ed02b9cec65e9a to your computer and use it in GitHub Desktop.
Save philipszdavido/47bb7ab62e665de7b5ed02b9cec65e9a to your computer and use it in GitHub Desktop.

How Angular resolves dynamic components.

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 JS before execution. Components, Modules, Directives are all turned into factories. In other words, Angular is made up of factories (ComponentFactory, NgModuleFactory).

So 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'
  }
}

won't be found on execution, it 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;

before being executed.

In this article we want to 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 view:

@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 ComponentFactory (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 its method 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 class 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 ComponentFactoryarray?

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 generates 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 { }

It's 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 methods reference the actual method implemetation, ɵcmf calls createNgModuleFactory, ɵmpd => moduleProviderDef, ɵmod => moduleDef.

We see that createNgModuleFactory takes a Module class, bootstrap components, and a module definition factory as arguments and returns a NgModuleFactory.

export function createNgModuleFactory(
    ngModuleType: Type<any>, bootstrapComponents: Type<any>[],
    defFactory: NgModuleDefinitionFactory): NgModuleFactory<any> {
  return new NgModuleFactory_(ngModuleType, bootstrapComponents, defFactory);
}

We will be 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 a 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 the 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 all through this to deduce how ComponentFactoryResolver gets its ComponentFactory array. Looking 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)

See that an array of ComponentFactory is passed to it. So during 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 on Dynamic Angular Components always refer to add all your dynamic components in the entryComponents array 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 knows 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, 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!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment