This tutorial is out of date and no longer maintained.
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.
View Angular 2 - Model Driven Forms (final) scotch on plnkr
We will build a form to capture user information based on this interface.
export interface User {
name: string; // required with minimum 5 characters
address?: {
street?: string; // required
postcode?: string;
}
}
Here is how the UI will look:
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
:
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; // our model driven form
public submitted: boolean; // keep track on whether form is submitted
public events: any[] = []; // use later to display form changes
constructor(private _fb: FormBuilder) { } // form builder simplify form initialization
ngOnInit() {
// we will initialize our form model here
}
save(model: User, isValid: boolean) {
this.submitted = true; // set form submit to true
// check if model is valid
// if valid, call API to save customer
console.log(model, isValid);
}
}
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)">
<!-- We'll add our form controls here -->
<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() {
// the long way
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() {
// the short way
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:
The address FormGroup contains 2 form controls:
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:
Street has only one required validator.
Let’s add the user’s name control to our view.
<!-- app.component.html -->
...
<!-- We'll add our form controls here -->
<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>
...
formControl
has no export value, we need to read the errors information from our form model.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.
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?
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.
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() {
// initialize stream
const myFormValueChanges$ = this.myForm.valueChanges;
// subscribe to the stream
myFormValueChanges$.subscribe(x => this.events
.push({ event: ‘STATUS CHANGED’, object: x }));
}
Then call this function in our ngOnInit()
.
ngOnInit() {
// ...omit for clarity...
// subscribe to form changes
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:
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.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!