Angular provides two ways to work with forms: template-driven forms and reactive forms, the latter also sometimes called model-driven forms. With template-driven forms, the default way to work with forms in Angular, template directives are used to build an internal representation of the form. With reactive forms, by contrast, you build your own representation of a form in the component class.
Reactive forms were introduced with Angular 2.
While reactive forms can be a tad more complex to work with in the beginning, they allow for much more flexibility and they also help keep your logic in the component class and your templates simple.
Here are some examples of things that are easy to do with reactive forms:
- Using custom validators
- Changing validation dynamically
- Dynamically adding form fields
Here’s how to get started with reactive forms in Angular:
Importing the ReactiveFormsModule
To work with reactive forms, you’ll be using the ReactiveFormsModule instead of the FormsModule, so import it in your app of feature module:
app.module.ts
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { HttpModule } from '@angular/http'; import { AppComponent } from './app.component'; @NgModule({ // ... imports: [ BrowserModule, ReactiveFormsModule, HttpModule ], // ... }) export class AppModule { }
Template: formGroup, ngSubmit & formControlName
With reactive forms the logic is declared entirely in the component class, so the template syntax is made very easy:
app.component.html
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <input formControlName="name" placeholder="Your name"> <input formControlName="email" placeholder="Your email"> <input formControlName="message" placeholder="Your message"> <button type="submit">Send</button> </form>
If you're using Angular 2.x, also add the novalidate directive with the opening form tag, as Angular overrides HTML5's validation. With Angular 4+, novalidate is automatically added behind the scenes.
Let’s break it down:
- formGroup: The form will be treated as a FormGroup in the component class, so the formGroup directive allows to give a name to the form group.
- ngSubmit: This is the event that will be triggered upon submission of the form.
- formControlName: Each form field should have a formControlName directive with a value that will be the name used in the component class.
The Component Class
And now in the component class we define our form group and individual form controls within our form group.
If a value if provided when newing a form control, it’ll be used as the initial value for the field.
Notice how the form group and form control names are the same that we used in our template, and how we initialize our form group in the ngOnInit lifecycle hook:
import { Component, OnInit } from '@angular/core'; import { FormGroup, FormControl } from '@angular/forms'; @Component({ // ... }) export class AppComponent implements OnInit { myForm: FormGroup; ngOnInit() { this.myForm = new FormGroup({ name: new FormControl('Benedict'), email: new FormControl(''), message: new FormControl('') }); } onSubmit(form: FormGroup) { console.log('Valid?', form.valid); // true or false console.log('Name', form.value.name); console.log('Email', form.value.email); console.log('Message', form.value.message); } }
Our onSubmit method is just a dummy one and doesn’t communicate the submitted form values to any external service or server, but it serves to show how we can access our form’s validity and form control values.
FormBuilder
We can make things simpler with the help of the FormBuilder helper, which allows us to forgo of all the newing of form group and form controls:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; @Component({ // ... }) export class AppComponent implements OnInit { myForm: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.myForm = this.fb.group({ name: 'Benedict', email: '', message: '' }); } // ... }
Validation
Adding validation to the form is very simple too. Just add the Validators class to your imports and declare your form controls with arrays instead of simple string values.
The first value in the array is the initial form value and the second value is for the validator(s) to use. Notice how multiple validators can be used on the same form control by wrapping them into an array:
import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @Component({ // ... }) export class AppComponent implements OnInit { myForm: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.myForm = this.fb.group({ name: ['Benedict', Validators.required], email: ['', [Validators.required, Validators.pattern('[a-z0-9.@]*')]], message: ['', [Validators.required, Validators.minLength(15)]] }); } }
Don't use the [a-z0-9.@]* pattern to validate email addresses. Here's a more robust pattern.
Accessing Form Value & Validity in the Template
In our template we can access each form control’s value and validity as well as the value and validity of the whole form group as a whole.
The following showcases a few ways to get to values, validity and errors:
<form [formGroup]="myForm" (ngSubmit)="onSubmit(myForm)"> <input formControlName="name" placeholder="Your name"> <p>Your choosen name: {{ myForm.get('name').value }}</p> <div *ngIf="myForm.get('name').hasError('required')"> Oops, please provide a name! </div> <input formControlName="email" placeholder="Your email"> <div *ngIf="myForm.controls.email.hasError('pattern')"> Oops, wrong pattern buddy! </div> <input formControlName="message" placeholder="Your message"> <div *ngIf="myForm.controls.message.errors.minlength"> Write something longer please! </div> <button type="submit" [disabled]="myForm.invalid"> Send </button> </form>
Notice how we can get to a control with either myForm.get(‘name’) or with myForm.controls.name, and we can get to errors with either .hasError(‘required’) or .errors.required. It’s a matter of preference which form you choose to use.
Notice also how be disable our submit button if our whole form is invalid.
Learning More
This post barely scratched the surface, and to learn more you can head over to the Reactive Forms section of the official Angular documentation.