ANGULAR 2 Component Factory can speed up software development by 60%

Angular 2 Component Factory

Sometimes we need to use components that are very much alike, having minimal differences between them. Form inputs are a typical example. In these instances, the software development time can be reduced considerably by abstracting common or generic characteristics of similar repetitive components.

In this article, we will be talking about Angular’s Component Factory and how one can benefit from it by creating components programmatically. We will be using MVC and object-oriented design patterns.

Abstracting a Basic Parent Component

With the object-oriented pattern in mind, let’s create a basic generic component to be imported and used by all the other children.

Generally speaking, a form input has a NAME and VALUE and should be referenced by a LABEL for accessibility. For the purpose of this post, ID and NAME attributes will be the same.

basic-input.component.ts

import { Component, Input} from '@angular/core'; @Component({ selector: 'basic-input', templateUrl: './basic-input.component.html', styleUrls: ['./basic-input.component.css'] }) export class BasicInputComponent { @Input() name: string; @Input() label: string; @Input() value: any; constructor() { } 

Common Text Input Component

common-input.component.ts

import { Component, Injector} from '@angular/core'; import { BasicInputComponent } from 'basic-input/basic-input.component'; @Component({ selector: 'common-input', templateUrl: './common-input.component.html', styleUrls: ['./common-input.component.css'] }) export class CommonInputComponent extends BasicInputComponent { constructor(injector: Injector) { super(injector); } }

Now that we’ve created a simple component that allows us to bind a value to its properties, we can use HTML to output the values configured at the component level:

<!-- common-input.component.html --> <label for="{{name}}">{{label}}:</label> <input id="{{name}}" name="{{name}}" type="text" value="{{value}}" /> 

CommonInputComponent inherits the properties from BasicInputComponent.

Component Factory
Angular’s ComponentFactory is nothing more than a factory of components; the name is self-explanatory. It is a very powerful way to dynamically add components to your application by using metadata.

First, let’s create a view container as the placeholder for our components and add a template reference to it:

<!-- fields.factory.component.html --> <div #dynamicComponentContainer></div>

Now, let’s create the file that will hold all the code for our factory component.

These are the imports necessary to make it work:

import { Component, Input, ViewContainerRef,ViewChild,ReflectiveInjector, ComponentFactoryResolver } from '@angular/core';

The next import is the component that will be automatically generated.

import { CommonInputComponent } from 'fields/inputs/common-input/common-input.component'; @Component({ selector: 'fields-factory', templateUrl: './fields-factory.component.html', styleUrls: ['./fields-factory.component.css'], entryComponents: [ CommonInputComponent ] })

Note that entryComponents is an array containing the components to be automatically factored.

To start our component class, we create an empty declaration of currentComponent.

currentComponent = null;

Now, we can access our view container and make the necessary changes to it.

@ViewChild('dynamicComponentContainer', { read: ViewContainerRef }) dynamicComponentContainer: ViewContainerRef;

Then, we can create a set method to manipulate the data from the DIV in the view. The component’s key is the class name for the component you want to create, while the input is an object with key/value pairs mapped to input-name/input-value, as follows:

@Input() set componentData (data: {component: any, inputs: any }) { if (!data) { return; }

The ReflectiveInjector resolves the input with dependency injections, providing the metadata for the component.

let inputProviders = Object.keys(data.inputs).map((inputName) => { return {provide: inputName, useValue: data.inputs[inputName]}; }); let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

From these providers, we inject the data into the element.

let injector = ReflectiveInjector .fromResolvedProviders(resolvedInputs, this.dynamicComponentContainer.parentInjector);

Then, we let the factory receive the component’s class.

let factory = this.resolver.resolveComponentFactory(data.component);

Finally, we use the factory to create the component.

let component = factory.create(injector);

And, we insert the component into the DOM object.

this.dynamicComponentContainer.insert(component.hostView);

And, we destroy the old component.

if (this.currentComponent) { this.currentComponent.destroy(); } this.currentComponent = component; }

We put the ComponentFactoryResolver into the constructor to declare the dependency.

constructor(private resolver: ComponentFactoryResolver) { }

Configuring the Basic Component

We have to adapt the basic input component to provide different inputs factory component’s injector. To do that, we must add an Injector into the component and declare the input as a getter.

basic-input.component.ts

import { Component, Input, Injector} from '@angular/core'; @Component({ selector: 'basic-input', templateUrl: './basic-input.component.html', styleUrls: ['./basic-input.component.css'] }) export class BasicInputComponent { @Input() name: string; @Input() label: string; @Input() value: any; constructor(public injector:Injector) { this.name = this.injector.get('name'); this.label = this.injector.get('label'); this.value = this.injector.get('value'); }

Field Factory Service

We can create a service to manage the components’ creation. Our factory expects to receive an object with key/values in order to create a component.

import { Injectable } from '@angular/core'; import { BasicInputComponent } from fields/basic-input/basic-input.component'; import { CommonInputComponent } from 'fields/inputs/common-input/common-input.component'; @Injectable() export class FieldFactoryService { setBasicInputComponent (component: any, name: string, label: string, value: string) : any { return { component: component, inputs: { name: name, label: label, value: value } } } setCommonInputComponent (name: string, label: string, value: string) : any { return this.setBasicInputComponent(CommonInputComponent, name, label, value); }

The setBasicInputComponent method creates the expected object for the factory, and the setCommonInputComponent defines the specific class for that component.

Using the factory

To use the factory, import the factory service where you want to use it:

import { FieldFactoryService } from 'services/field-factory/field-factory.service';

Pass FieldFactoryService to the constructor:

constructor(public ffService: FieldFactoryService ){}

Use the method of the component that you want to create and pass the parameters as many times as you need:

this.ffService.setCommonInputComponent( 'fooId', 'fooLabel', 'fooValue'); this.ffService.setCommonInputComponent( 'barId', 'barLabel', 'barValue');

Voilá, you just used one component to create two different ones with different configurations but the same characteristics. This may seem complicated, but it makes a significant difference in more complex applications.

Conclusions

With the of use of ComponentFactory we can speed up the development process, since we don’t need to repeatedly create components just to change some configurations that are similar but that we use in different places. The process can be repeated in different approaches, but it’s currently very underrated. DRY (Don’t Repeat Yourself) processes must be exploited further in order to make better code and increase the speed of development.