There are two ways to build forms in Angular 2, namely template-driven and model-driven.

In this article, we will learn about building model-driven form with validation using the latest forms module, then we will talk about what are the advantages / disadvantages of using model driven form as compared to template-driven form.

Please refer to How to Build Template-driven Forms in Angular 2 if you would like to learn about template-driven forms.

We will build a form to capture user information based on this interface.

export interface User { name : string ; address ? : { street ? : string ; postcode ? : string ; } }

Here is how the UI will look:

Requirements

Show error message only when: the field is invalid and it’s dirty (the field is touched/edited), or

and it’s (the field is touched/edited), or the field is invalid and the form is submitted Listen and display form changes: when any form values change

when form status (form validity) change Update the initial name field value to ‘John’ without trigger form changes.

Here's our file structure:

|- app/ |- app.component.html |- app.component.ts |- app.module.ts |- main.ts |- user.interface.ts |- index.html |- styles.css |- tsconfig.json

In order to use new forms module, we need to npm install @angular/forms npm package and import the reactive forms module in application module.

$ npm install @angular/forms --save

Here's the module for our application app.module.ts :

Upgrade Your JS Go from vanilla JavaScript 👉 React

Watch for FREE

import { NgModule } from '@angular/core' ; import { BrowserModule } from '@angular/platform-browser' ; import { FormsModule , ReactiveFormsModule } from '@angular/forms' ; import { AppComponent } from './app.component' ; @ NgModule ( { imports : [ BrowserModule , ReactiveFormsModule ] , declarations : [ AppComponent ] , bootstrap : [ AppComponent ] } ) export class AppModule { }

Let's move on to create our app component.

import { Component , OnInit } from '@angular/core' ; import { FormGroup , FormControl , FormBuilder , Validators } from '@angular/forms' ; import { User } from './user.interface' ; @ Component ( { moduleId : module . id , selector : 'my-app' , templateUrl : 'app.component.html' , } ) export class AppComponent implements OnInit { public myForm : FormGroup ; public submitted : boolean ; public events : any [ ] = [ ] ; constructor ( private _fb : FormBuilder ) { } ngOnInit ( ) { } save ( model : User , isValid : boolean ) { this . submitted = true ; console . log ( model , isValid ) ; } }

Notes

myForm will be our model driven form. It implements FormGroup interface. FormBuilder is not a mandatory to building model driven form, but it simplify the syntax, we’ll cover this later.

This is how our HTML view will look like.

< form [formGroup] = " myForm " novalidate (ngSubmit) = " save(myForm.value, myForm.valid) " > < button type = " submit " > Submit </ button > </ form >

We make sure we bind formGroup to our myForm property in app.component.ts file.

We'll handle the form submit ( ngSubmit ) event in save() function that we defined in our app.component.ts file.

All set! Let's implement our model-driven form.

There are two ways to initialize our form model using model-driven forms in Angular 2.

Here is the long way to define a form:

ngOnInit ( ) { this . myForm = new FormGroup ( { name : new FormControl ( '' , [ < any > Validators . required , < any > Validators . minLength ( 5 ) ] ) , address : new FormGroup ( { street : new FormControl ( '' , < any > Validators . required ) , postcode : new FormControl ( '8000' ) } ) } ) ; }

And here's the short way (using the form builder):

ngOnInit ( ) { this . myForm = this . _fb . group ( { name : [ '' , [ < any > Validators . required , < any > Validators . minLength ( 5 ) ] ] , address : this . _fb . group ( { street : [ '' , < any > Validators . required ] , postcode : [ '' ] } ) } ) ; }

Both of these options will achieve the same outcome. The latter just has a simpler syntax.

A form is a type of FormGroup. A FormGroup can contain one FormGroup or FormControl. In our case, myForm is a FormGroup. It contains:

A name FormControl

FormControl An address FormGroup

The address FormGroup contains 2 form controls:

street

postcode

We can define a validator for both FormGroup and FormControl. Both accept either a single validator or array of validators.

Angular 2 comes with a few default validators and we can build our custom validator too. In our case, name has two validators:

required

minLength

Street has only one required validator.

Let’s add the user's name control to our view.

... < div > < label > Name </ label > < input type = " text " formControlName = " name " > < small [hidden] = " myForm.controls.name.valid || (myForm.controls.name.pristine && !submitted) " > Name is required (minimum 5 characters). </ small > </ div > ...

We haved assigned name to formControlName

For validation, since formControl has no export value, we need to read the errors information from our form model.

has no export value, we need to read the errors information from our form model. In our case, to check if name field is valid, or if it’s pristine, we’ll need to get the value from myForm controls, e.g. myForm.controls.name.valid. Very long syntax, huh.

Add an address form group to the view

Next we’ll add our address form group to the view.

.... < div formGroupName = " address " > < label > Address </ label > < input type = " text " formControlName = " street " > < small [hidden] = " myForm.controls.address.controls.street.valid || (myForm.controls.address.controls.street.pristine && !submitted) " > street required </ small > </ div > < div formGroupName = " address " > < label > Postcode </ label > < input type = " text " formControlName = " postcode " > </ div > ...

We have assigned the group name address to formGroupName. Please note that formGroupName can be used multiple times in the same form. In many examples, you’ll see people do this:

... < div formGroupName = " address " > < input formControlName = " street " > < input formControlName = " postcode " > </ div > ...

This gives us the same results as above:

... < div formGroupName = " address " > < input formControlName = " street " > </ div > < div formGroupName = " address " > < input formControlName = " postcode " > </ div > ...

This is the same process as the previous section to bind form control.

Now the syntax gets even longer to retrieve control information. Oh my, myForm.controls.address.controls.street.valid .

Now, imagine we need to assign default user’s name John to the field. How can we do that?

The easiest way is if John is static value:

... this . myForm = this . _fb . group ( { name : [ 'John' , [ < any > Validators . required , < any > Validators . minLength ( 5 ) ] ] } ) ; ...

What if John is not a static value? We only get the value from API call after we initialize the form model. We can do this:-

... ( < FormControl > this . myForm . controls [ 'name' ] ) . setValue ( 'John' , { onlySelf : true } ) ; ...

The form control exposes a function call setValue which we can call to update our form control value.

setValue accept optional parameter. In our case, we pass in { onlySelf: true }, mean this change will only affect the validation of this control and not its parent component.

By default this.myForm.controls['name'] is of type AbstractControl. AbstractControl is the base class of FormGroup and FormControl. Therefore, we need to cast it to FormControl in order to utilize control specific function.

How about updating the whole form model?

It's possible! We can do something like this:

... const people = { name : 'Jane' , address : { street : 'High street' , postcode : '94043' } } ; ( < FormGroup > this . myForm ) . setValue ( people , { onlySelf : true } ) ; ...

Now that we’ve build our model driven form. What are the advantages of using it over template driven form?

Unit testable

Since we have the form model defined in our code, we can unit test it. We won’t discuss detail about testing in this article.

Listen to form and controls changes

With reactive forms, we can listen to form or control changes easily. Each form group or form control expose a few events which we can subscribe to (e.g. statusChanges, valuesChanges, etc).

Let say we want to do something every time when any form values changed. We can do this:-

subcribeToFormChanges ( ) { const myFormValueChanges$ = this . myForm . valueChanges ; myFormValueChanges$ . subscribe ( x => this . events . push ( { event : ‘ STATUS CHANGED ’ , object : x } ) ) ; }

Then call this function in our ngOnInit() .

ngOnInit ( ) { this . subcribeToFormChanges ( ) ; }

Then display all value changes event in our view.

... Form changes: < div *ngFor = " let event of events " > < pre > {{ event | json }} </ pre > </ div > ...

We can imagine more advanced use cases such as changing form validation rules dynamically depends on user selection, etc. Model driven form makes this simpler.

It depends. If you are not doing unit testing (of course you should!), or you have simple form, go ahead with template-driven forms.

If you are not doing unit testing or you have simple form, go ahead with template-driven forms.

If you have advanced use cases, then consider model driven form.

Something good about template-driven forms as compared to model driven forms, imho:

Template driven form has form.submitted flag in the exported ngForm, while model driven form don’t have that. Reading form control property in model driven form is not syntax friendly in the view. In our example, the syntax of reading address validity is myForm.controls.address.controls.street.valid while in template driven form we have exported ngModel, the syntax can be shorten to just street.valid. While understand that no exported member is done on purpose in the new design, the long syntax is still eye hurting… More familar syntax if you are coming from Angular 1.

That’s it! Now that you know how to build model-driven form, how about complex and nested model-driven forms? Says, we allow the user to enter multiple addresses now, how can we handle form array and validation? You might be interest in How to Build Nested Model-driven Forms in Angular 2.

Happy coding.

Like this article? Follow @JecelynYeen on Twitter