Create a new project using
ng new dynamic-formsand then navigate to the dynamic-forms folder.
If you are using the latest version of the angular-cli it will ask you if you want to add routing, for this project I selected
no and what kind of stylesheet format do you want to use for which I selected
sass. So that will create a basic angular project for us to get started.
ng serve should get us up and running. More info about the @angular/cli can be found here.
Creating our dynamic-form component
This component will be our wrapper for dynamically creating our form. So to do that simply generate a new component using angular-cli command
ng generate component
ng generate component components/dynamic-form
That will create a new component for us and now we can make use of it, for now let’s replace our app.component.html content with our new created component, that’s
Now we have our basic project structure, it’s time to move ahead with our logic for creating a dynamic form.
First of all let’s define our form data model using an interface. Let’s say for instance that our data model will be the following:
This will help us to type our mock data in order to keep it consistent with our defined model. In our real world use case this will type the server seed data which will dynamically create our form.
For our use case we will be using Angular Reactive Form approach, so in order to do that we need to add the following imports into our
Full documentation for angular reactive forms can be found here
This will help us with all the API required for using reactive forms, in our case we will be using FormGroup and FormControl to keep things simple.
From the official angular docs.
- FormControl: “Tracks the value and validation status of an individual form control.”
- FormGroup: “Tracks the same values and status for a collection of form controls.”
Let’s get to our dynamic-form component, so the first thing we need to do is to create our basic form. As the previous definition says, in order to track values and status for a collection of form controls AKA inputs we need to create a new instance of a FormGroup in our component.
This will be for now our DynamicForm component. We’ve just imported FormGroup in order to create a FormGroup instance for our dynamic form. This way we can tie up our template form with the logic of our component, and the way to tie these two together is by using the [FormGroup] attribute binding in our template, passing our
form: FormGroupcreated in our component as follows.
By doing this now we have a direct connection between our form in the template and the logic behind it in our component.
Everything so good so far, things are becoming more interesting. For our use case we will be using some mock data in order to recreate some kind of data fetching from a remote location — lets say an API —
So we will have some mock data based on the FormData interface defined previously. This basically will provide us the basic information for every of our form fields like the name, placeholder, type, validators, etc.
To make things simple we will import our mock data into our
app.component.tsand then pass it down to our dynamic-form component as an @Input().
So we created an instance of a MockForm object. Following next we will be accepting an
@Input() formDataof type
FormData. Then, we are accepting the data mockForm instance in our dynamic-form template
In a real world scenario, this data should be retrieved from an external API or something similar.
Now we have our data available in our component, the next step is to create the FormGroup with each of the FormControl’s or form fields that our dynamic form will have. That will take place in our ngOnInit lifecycle hook.
We have just simply looped through the formData, and created a new FormControl for every form field in the array. Then we instantiate a new FormGroup using those FormControls to keep track of them.
The only thing missing now is to track every form field in our form template by using the already created FormGroup instance.
Now we need a way for dynamically create our template, the most simple approach for this is using the ngSwitch directive. First of all we need to loop over the formData in order to get access to every formData element. And next just switch over every controlType to get to know what kind of input we need to display on the UI.
ControlType was just a generic name, you can use whatever name you want to define for the properties of your dynamic form.
As we are already looping through the data, the next step to follow is to ask for what kind of input do we want to display on the UI.
By using ngSwitchCase we can validate what kind of control type we have as an element in the loop run. In the first case let’s handle the scenario where we have an input of type ‘text’.
First of all we need to bind the controlName into the formControName in order to point to our previously created FormControl instance. Note that we are using the controlName property of our data as we previously did in the creation of the FormGroup instance in our component. This is a MUST in order to tie up the template inputs with the form fields instances.
Then we just bound type and name attributes with the valueType and controlName data properties respectively. Finally for the sake of simplicity we bound the basic validations into our input template. This also could have been done in the component when we were creating our FormGroup instance. It’s just matter of personal preference.
And last but not least, we are handling error use cases based on the validity of the form. You can learn more about form error and validations here.
We can do the same for allowing another input types like ‘radio’ and ‘selects’ in our case this will be the template for creating both of them.
And with this, we have all in order to dynamically render our form based on some mock data. The following codesandbox is a running example of our use case.