Configurable Reactive Forms in Angular with dynamic components

In this post we’re going to explore the creation of dynamic components alongside a Reactive Form setup. If you’re new to Reactive Forms, check out on of my previous posts on it before diving in!
This is what we’ll be building with fully dynamic components (yes it’s not the most complex of forms, but we’re diving into the concept of how to dynamically render form components based off a configuration object):

Dynamic components are useful when we want to generate components on the fly, for example we could assume a server response tells us to display a particular view and/or message, and handling this with built-in structural directives (such as a big ngIf tree) is not really the best approach – we can do better!
Another powerful use case is having a form driven by configuration. This allows us to develop a generic form component, with the child nodes being generated from a descriptor. Let’s go ahead and see how this would be done, whilst harnessing the power of Angular’s ReactiveFormsModule to create awesome forms.
Table of contents

Component Anatomy

The Wrapper
The View

Instantiating components

Component Factories and the HostView

Creating a dynamic form

DynamicFormModule
The main container
Using the dynamic form
Input field
Select field
Button
DynamicField
Looping through the fields

Submitting the form
Conclusion

Component Anatomy
Components aren’t actually as straight-cut as they first appear. Angular’s compiler actually splits components out into two different things. Let’s take a look at what actually goes on behind the scenes.

Want to go straight to the code?

The Wrapper
First of all, a “wrapper” is created. This deals with communicating with the actual component class that we define. When the wrapper is initialised, it initiates an instance of the component class.
It’s also responsible for change detection – methods are created on this class for each @Input that a component has, and checks their value and updates it if necessary.
The wrapper also deals with triggering various lifecycle hooks that are defined on the original component class, such as ngOnInit and ngChanges.
The View
Secondly, something called a “view” is created. This is responsible for creating our template with the platform renderer, as well as triggering the wrapper’s change detection (and some other internal methods).
Each component can be composed of multiple views. When we use a structural directive such as an ngIf or ngFor, a separate view is created. These views contain the content of the element that the directive was applied to. This is called an “embedded view”.
This is extremely powerful – for example, as the content of an ngFor is made into a self-contained view, it can be created and destroyed with just two function calls. There’s no need for the main component view to work out what to add or remove from the DOM – the embedded view (created by the ngFor) knows what it has created and what it needs to destroy.
Instantiating components
When components are instantiated they need a “host” view to live in – which may or may not exist.
Components that are used inside an existing component (which we do the majority of the time) already have a host view – the view that is using the component. This deals with the creation of the DOM node for our component selector, as well as the wrapper and the component’s main view for us.
However, host views don’t always exist. When we bootstrap our application there’s no existing Angular view to contain the component.
This is also true for when we dynamically create components – although we may insert the component into an existing view. Any views we inject a dynamic component into do not contain the logic to instantiate the dynamic component (as this is handled by the compiler for non-dynamic components).
We can also choose to insert a component next to the component that we’re dynamically creating it in, rather than inside. You will have seen this in action if you use router-outlet.

Angular’s router-outlet is just a directive – meaning it doesn’t have a view for the component to be inserted into.

Component Factories and the HostView
This is where component factories come into play. When our component code is compiled, it also outputs something called a component factory, as well as another view, titled Host.
A host view is a thin view that deals with creating our component for us, in lieu of an existing component view. It creates the DOM node for the component’s selector, as well as initialises the wrapper and the main view, much like what we touched on above.

The component factory is just an instance of a core Angular class, the ComponentFactory.

Once the component is created, the host view can then be attached anywhere inside the parent component’s view, e.g. inside of a ViewContainerRef.

When Angular creates a component, if that component injects a ViewContainerRef, it creates a view container for that component. This so the component can create and manipulate nested views within the root DOM node of that component.

Creating a dynamic form
Now that we’ve got the theory out of the way, we can continue on to creating a dynamic form. Let’s kick things off by creating the module for our dynamic forms.

Grab the seed project here

Follow the setup instructions inside the readme file.

View the final source code

DynamicFormModule
Our dynamic form is going to be an importable module, much like the ReactiveFormsModule that @angular/forms provides. When we import the module, we can then access everything we need to create a dynamic form.
Go ahead and create a /dynamic-form/ directory inside of /app.
**/app/dynamic-form

Then create a file called dynamic-form.module.ts. To start, it will look like this:
import { NgModule } from ‘@angular/core’;
import { CommonModule } from ‘@angular/common’;
import { ReactiveFormsModule } from ‘@angular/forms’;

@NgModule({
imports: [
CommonModule,
ReactiveFormsModule
]
})
export class DynamicFormModule {}

The final thing we need to do with the module (for now), is import it into our AppModule inside /app/app.module.ts:
import { NgModule } from ‘@angular/core’;
import { BrowserModule } from ‘@angular/platform-browser’;

import { AppComponent } from ‘./app.component’;

import { DynamicFormModule } from ‘./dynamic-form/dynamic-form.module’;

@NgModule({
imports: [
BrowserModule,
DynamicFormModule
],
bootstrap: [
AppComponent
],
declarations: [
AppComponent
]
})
export class AppModule {}

Now we need to create the container that will be used to make a dynamic form!
The main container
The point of entry for our dynamic form is the main container. This will be the only component that is exposed by our dynamic forms module, being responsible for accepting a form configuration and creating the form.
Create a directory inside of the /dynamic-form directory you’ve just made called /containers. Inside of that, create a directory called /dynamic-form.
**/app/dynamic-form/containers/dynamic-form

Inside of that directory, create a component file called dynamic-form.component.ts.
import { Component, Input, OnInit } from ‘@angular/core’;
import { FormGroup, FormBuilder } from ‘@angular/forms’;

@Component({
selector: ‘dynamic-form’,
styleUrls: [‘dynamic-form.component.scss’],
template: `