In this tutorial, I am talking about two approaches that Angular offers - Template Driven and Data Driven (Reactive Approach).
Template Driven approach is the traditional approach which we are using since Angular 1.x. Even in Angular two way binding, we use the same approach.
Data Driven or Reactive Forms Module is different than the template driven approach. In the reactive approach, you create a tree of Angular form objects using form builder in the component class (i.e. your .ts file ) and bind them to native form control elements in the component template (i.e. your .html file). It is more or less the same as what we do in ASP.NET MVC (Model).
One advantage of directly working with form control objects is that the value and validity updates are always synchronous and under your control. You won't encounter the timing issues that sometimes plague a template-driven form, and reactive forms are way easier to unit test. (Source Courtesy Angular.io).
Which one is better - Template Driven or Reactive??
Neither Reactive nor Template Driven are better over each other. They both are different approaches, so you can use whichever suits your needs the most. You can even use both in the same application.
So, in this tutorial, I am talking about the following terms.
- Reactiveform Module
- Form Control
- FormGroup
- FromBuilder
- Validators
- And more..
Now enough of introduction and theory, let’s dive into the code. The environment used by me to create this demo is Visual Studio Code as IDE, and Angular-Cli 1.1.3.
In order to use Reactive forms module, we need to import it from @angular/forms, as shown below, and declare it in imports array of app.module.ts file.
- 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({
- declarations: [
- AppComponent
- ],
- imports: [
- BrowserModule,
- ReactiveFormsModule,
- HttpModule
- ],
- providers: [],
- bootstrap: [AppComponent]
- })
- export class AppModule {}
Now, we will generate a new component using angular-cli.
ng g c products
products.component.html
- <div class="container">
- <div class="row">
- <div *ngFor="let item of allProduct" class="col-sm-6 col-md-4">
- <div class="thumbnail"> <img height="250" src="{{item.pimg}}" alt="...">
- <div class="caption">
- <h3>{{item.pname}}</h3>
- <p>{{item.pprice | currency:'INR':true }}</p>
- <p><a class="btn btn-primary" role="button">{{item.soh}}</a> <a class="btn btn-default" role="button">Button</a></p>
- </div>
- </div>
- </div>
- </div>
- </div>
products.component.ts
- import {
- Component,
- OnInit
- } from '@angular/core';
- @Component({
- selector: 'app-products',
- templateUrl: './products.component.html',
- styleUrls: ['./products.component.css']
- })
- export class ProductsComponent implements OnInit {
- allProduct: any = [{
- "p_id": 1,
- "pname": "omega",
- "pprice": 100000,
- "pimg": "http://cdn2.jomashop.com/media/catalog/product/o/m/omega-seamaster-planet-ocean-black-dial-men_s-watch-232.30.42.21.01.001_1.jpg",
- "soh": 5
- }, {
- "p_id": 2,
- "pname": "timex",
- "pprice": 2000,
- "pimg": "http://ecx.images-amazon.com/images/I/51Ezfl22mYL._AC_UL260_SR200,260_.jpg",
- "soh": 10
- }, {
- "p_id": 3,
- "pname": "titan",
- "pprice": 12000,
- "pimg": "http://ecx.images-amazon.com/images/I/51fnWFY3s3L._AC_UL260_SR200,260_.jpg",
- "soh": 15
- }, {
- "p_id": 4,
- "pname": "fossil",
- "pprice": 18000,
- "pimg": "http://i.ebayimg.com/00/s/NjAwWDYwMA==/z/ZN8AAOSwd4tT5KNF/$_32.JPG",
- "soh": 8
- }, {
- "p_id": 5,
- "pname": "rolex",
- "pprice": 125000,
- "pimg": "http://dvciknd2kslsk.cloudfront.net/images/watchfinderimages/Watch/Rolex/GMT-Master-II/76399-2005568029.jpg",
- "soh": 3
- }, {
- "p_id": 6,
- "pname": "fast track",
- "pprice": 3000,
- "pimg": "http://www.shadestation.co.uk/media/product_images/Uboat-Watches-Titaniumd.gif",
- "soh": 10
- }, {
- "p_id": 7,
- "pname": "citizen",
- "pprice": 15000,
- "pimg": "https://static2.ethoswatches.com/media/catalog/product/cache/1/small_image/498x750/9df78eab33525d08d6e5fb8d27136e95/c/i/citizen-casual-an8070-53a.jpg",
- "soh": 5
- }, {
- "p_id": 8,
- "pname": "guess",
- "pprice": 15800,
- "pimg": "https://static2.ethoswatches.com/media/catalog/product/cache/1/small_image/498x750/9df78eab33525d08d6e5fb8d27136e95/a/n/an8070-53a.jpg",
- "soh": 10
- }];
- constructor() {}
- ngOnInit() {}
So far, we have created one component and displayed the products from the array. Output should be something like below.
Now, as the product display is done, we want to create a product addition form using reactive form approach. So, first of all, in our component, we need to import the following packages to our TypeScript file, i.e., products.component.ts
- import { FormGroup,FormBuilder,Validators,FormControl } from '@angular/forms';
Then, create an object of FormGroup and name it as myForm.
- export class ProductsComponent implements OnInit {
- myForm: FormGroup;
- constructor(private formbuilder: FormBuilder) {}
- }
Now, in ngOnInit method, create the instance of myForm as shown below. Also, define the rule for validations, Here, I am using both validations, inbuilt validations as well as custom validations.
- ngOnInit() {
- this.myForm = this.formbuilder.group({
- 'p_id': ['', Validators.required],
- 'pname': ['', [Validators.required, this.exampleValidator]],
- 'pprice': ['', [Validators.required, Validators.min(0)]],
- 'pimg': ['', Validators.required],
- 'soh': ['', [Validators.required, Validators.max(10)]]
- });
- }
- exampleValidator(control: FormControl): {
- [s: string]: boolean
- } {
- if (control.value === "Example") {
- return {
- example: true
- };
- }
- return null;
- }
Here, on price column, I have applied two validations - one is required and the other one's minimum value must be positive. And in pname column, I have shown the demo for custom validation named as examplevalidator, in which I will throw the error if the name of product = “Example”. In this way, we can define in-built as well as custom validators in Angular.
Entire products.component.ts is as shown below.
- import {
- Component,
- OnInit
- } from '@angular/core';
- import {
- FormGroup,
- FormBuilder,
- Validators,
- FormControl
- } from '@angular/forms';
- @Component({
- selector: 'app-products',
- templateUrl: './products.component.html',
- styleUrls: ['./products.component.css']
- })
- export class ProductsComponent implements OnInit {
- myForm: FormGroup;
- constructor(private formbuilder: FormBuilder) {}
- onSubmit() {
- this.allProduct.push(this.myForm.value);
- }
- ngOnInit() {
- this.myForm = this.formbuilder.group({
- 'p_id': ['', Validators.required],
- 'pname': ['', [Validators.required, this.exampleValidator]],
- 'pprice': ['', [Validators.required, Validators.min(0)]],
- 'pimg': ['', Validators.required],
- 'soh': ['', [Validators.required, Validators.max(10)]]
- });
- }
- exampleValidator(control: FormControl): {
- [s: string]: boolean
- } {
- if (control.value === "Example") {
- return {
- example: true
- };
- }
- return null;
- }
- }
Here is the code for products.component.html
- <div class="container">
- <h1>Add Product Demo</h1>
- <form [formGroup]="myForm" (ngSubmit)="onSubmit()">
- <div class="form-group"> <label for="Id">p_id</label> <input formControlName="p_id" type="number" id="p_id" class="form-control"> </div>
- <div class="alert alert-danger" *ngIf="myForm.get('p_id').hasError('required') && myForm.get('p_id').touched"> p_id is required </div>
- <div class="form-group"> <label for="pname">pname</label> <input formControlName="pname" type="text" id="pname" class="form-control"> </div>
- <div class="alert alert-danger" *ngIf="(myForm.get('pname').hasError('required') || myForm.get('pname').invalid ) && myForm.get('pname').touched "> product name is required and it shouldn't be Example </div>
- <div class="form-group"> <label for="pprice">pprice</label> <input formControlName="pprice" type="text" id="pprice" class="form-control"> </div>
- <div class="alert alert-danger" *ngIf="(myForm.get('pprice').hasError('required') || myForm.get('pprice').invalid) && myForm.get('pprice').touched"> pprice is required and must be positive value </div>
- <div class="form-group"> <label for="pimg">pimg</label> <input formControlName="pimg" type="text" id="pimg" class="form-control"> </div>
- <div class="alert alert-danger" *ngIf="myForm.get('pimg').hasError('required') && myForm.get('pimg').touched"> pimg is required </div>
- <div class="form-group"> <label for="soh">soh</label> <input formControlName="soh" type="text" id="soh" class="form-control"> </div>
- <div class="alert alert-danger" *ngIf="(myForm.get('soh').hasError('required') || myForm.get('soh').invalid) && myForm.get('soh').touched"> soh is required and maximum soh would be 10 </div> <button type="submit" [disabled]="!myForm.valid" class="btn btn-primary">Add Product</button> </form>
- </div>
In this products.component.html page, the most important thing is the name of the form.
[formGroup]="myForm"
Which should be same as the name of myForm:FormGroup.
formControlName="pname” attribute in input tag will bind it to the property of formbuilder group. Remember, we created it in our TypeScript.