This guide was written for Angular 2 version: 2.0.0
The ng-model
directive in Angular 1.x allows us to create two-way data binding between a form control and a property on scope. In this guide we'll be converting an Angular 1.x ng-model
directive into Angular 2's ngModel
directive.
Primarily, we use ng-model
to keep a form input in sync with a property on scope in Angular 1.x. There are some additional responsibilities that ng-model
handles but we will start out by focusing on the data binding facet of the directive.
The most common use case for ng-model
is binding a text input to a property and so we will start there. In our component controller, we will create a myModel
object with a username
property that we will bind to.
function AppComponentCtrl() {
this.myModel = {
username: 'poweruser'
}
}
To bind a text input to our myModel.username
property, we can define a text input control and just add ng-model="$ctrl.myModel.username"
to it. Now whenever we type something into our input field, the myModel.username
property will be updated with the new value.
<input type="input" ng-model="$ctrl.myModel.username" placeholder="Username">
We could technically stop here as we have captured the essence of ng-model
with a few lines of code but let's go a bit further and see the role that ng-model
plays in the bigger picture. Two-way data binding is a really handy tool to have at our disposal but ng-model
can also register itself with its parent form and communicate validation behavior and state.
To see this in action, we will wrap our input in a form
element. We will give our form element a name of myForm
and our input a name of username
. Angular uses name
attributes to register form controls with the form. We will also add a required
attribute to our input so that we have something to validate against.
<form name="myForm" novalidate>
<div class="form-group">
<label for="exampleInput">Username</label>
<input type="input" name="username" ng-model="$ctrl.myModel.username" required class="form-control" id="exampleInput" placeholder="Username">
</div>
</form>
For the sake of illustration, we will add a couple pre
tags to our template and bind to $ctrl.model
and myForm
respectively with the json
pipe.
<pre class="highlight">{{$ctrl.myModel | json}}</pre>
<pre class="highlight">{{myForm | json}}</pre>
This is a handy trick for serializing an object and displaying it in our template. The current state of $ctrl.myModel
will look something like the JSON object below.
{
"username": "poweruser"
}
The output of myForm
is fairly interesting in that it contains all sorts of information about not only the state of the form such as $dirty
, $valid
, $submitted
, etc but also about the username
input. If you recall, we added the name
attribute to our input with the value of username
which is why we see a username
property on our form object. Because we have not touched the input, it is an $untouched
state and currently $valid
because we are binding it to a property that is not an empty string.
{
"$error": {},
"$name": "myForm",
"$dirty": false,
"$pristine": true,
"$valid": true,
"$invalid": false,
"$submitted": false,
"username": {
"$viewValue": "poweruser",
"$modelValue": "poweruser",
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": true,
"$touched": false,
"$pristine": true,
"$dirty": false,
"$valid": true,
"$invalid": false,
"$error": {},
"$name": "username",
"$options": null
}
}
If we delete the text inside our input, a few interesting things happen. The first being that $ctrl.myModel
becomes an empty object because username
is now an empty string.
{}
More importantly, there is an error on the form object which we can see in the $error
property. We can also see the error at the form control level so that we if we wanted to set up error messages per control, we would not have to bind to the entire form object.
{
"$error": {
"required": [
{
"$viewValue": "",
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": false,
"$touched": true,
"$pristine": false,
"$dirty": true,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "username",
"$options": null
}
]
},
"$name": "myForm",
"$dirty": true,
"$pristine": false,
"$valid": false,
"$invalid": true,
"$submitted": false,
"username": {
"$viewValue": "",
"$validators": {},
"$asyncValidators": {},
"$parsers": [],
"$formatters": [
null
],
"$viewChangeListeners": [],
"$untouched": false,
"$touched": true,
"$pristine": false,
"$dirty": true,
"$valid": false,
"$invalid": true,
"$error": {
"required": true
},
"$name": "username",
"$options": null
}
}
We can also use ng-model
to bind to other form controls such as select
, radio
and checkbox
. Let's update our myModel
object with some additional properties so that we can bind to them in our template.
function AppComponentCtrl() {
this.myModel = {
username: 'poweruser',
items: [
{ id: 1, label: 'Item One' },
{ id: 2, label: 'Item Two' },
{ id: 3, label: 'Item Three' }
],
selectedItem: null,
selectedColor: 'red',
isChecked: true
}
// Pre-select item
this.myModel.selectedItem = this.myModel.items[0];
}
Here is the updated template with additional form controls bound to myModel
using ng-model
.
<div class="row">
<div class="col-sm-6">
<form name="myForm" novalidate>
<div class="form-group">
<label for="exampleInput">Username</label>
<input type="input" name="username" ng-model="$ctrl.myModel.username" required class="form-control" id="exampleInput" placeholder="Username">
</div>
<div class="form-group">
<label for="exampleSelect">Example select</label>
<select ng-options="item as item.label for item in $ctrl.myModel.items"
ng-model="$ctrl.myModel.selectedItem" class="form-control" id="exampleSelect">
</select>
</div>
<fieldset class="form-group">
<legend>Radio buttons</legend>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" ng-model="$ctrl.myModel.selectedColor" name="optionsRadios" value="red"> Red
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" ng-model="$ctrl.myModel.selectedColor" name="optionsRadios" value="green"> Green
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" class="form-check-input" ng-model="$ctrl.myModel.selectedColor" name="optionsRadios" value="blue"> Blue
</label>
</div>
</fieldset>
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" ng-model="$ctrl.myModel.isChecked" class="form-check-input"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<div class="col-sm-6">
<pre class="highlight">{{$ctrl.myModel | json}}</pre>
<pre class="highlight">{{myForm | json}}</pre>
</div>
</div>
The Angular 2 implementation of the ng-model
is called ngModel
, purposely in camelCase. On the surface, the essence of ngModel
is identical to its Angular 1.x counterpart in that it supplies two-way data binding between the template and the component class. The underlying implementation is completely different and this what we will discuss in the next section.
The first fundamental difference between Angular 1.x and Angular 2 is that we need to include the FormsModule
in our Angular 2 application for forms to even work. Forms functionality was separated from Angular 2 core so that we could compose our application to use alternative forms modules or none at all if our application did not require it.
To surface the FormsModule
functionality to our application, we will import it in our in our AppModule
file and then add it to the imports
property.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
As in the Angular 1.x version, we need to set up our component class to satisfy our template. We have a myModel
object with a username
property that we will bind to.
export class AppComponent implements OnInit {
myModel = {
username: 'poweruser'
}
ngOnInit() {}
}
To set up two-way data binding with ngModel
, we will add an input to our template and bind to our username
property with [(ngModel)]="myModel.username"
. The obvious question is "What is up with the funny syntax around ngModel?" and this is where we completely diverge from how Angular 1.x implemented ng-model
.
In Angular 2, we can bind properties on our component to our template using property binding syntax which looks like [property]="value"
. We can also communicate events from the template to our component using event syntax which looks like (event)="handler()"
. Each binding is distinctly uni-directional but we can combine them to create bi-directional binding which looks like [(ngModel)]="property"
.
<input [(ngModel)]="myModel.username" type="input" class="form-control" placeholder="Username">
Two-way data binding with ngModel
is accomplished by combining two one-way data binding mechanisms to create the appearance of two-way data binding. If we tried to use ngModel
without binding as seen in the code below, our template would just render the text value in the attribute which would be myModel.username
.
<input ngModel="myModel.username"
type="input" class="form-control" placeholder="Username">
If we add in property binding, the input will render with the value of myModel.username
which is poweruser
.
<input [ngModel]="myModel.username"
type="input" class="form-control" placeholder="Username">
The problem is that though we are displaying the property, we have no way of communicating any additional changes back to the component. Fortunately, ngModel
emits an internal event called ngModelChange
which we can bind to. By adding (ngModelChange)="myModel.username = $event"
to our template, we are listening for the ngModelChange
event and then assigning the value of the $event
to myModel.username
.
<input [ngModel]="myModel.username" (ngModelChange)="myModel.username = $event"
type="input" class="form-control" placeholder="Username">
This is slightly verbose and so we can just combine the two bindings together into the more conventional form below.
<input [(ngModel)]="myModel.username" type="input" class="form-control" placeholder="Username">
Let's register our input with a parent form to see the role that ngModel
plays outside of just data binding. We have created a form and then created a local template variable called myForm
with #myForm="ngForm"
. We also need to add a name
property to our input so that it is registered with the form and a required
property so we can validate our input.
<form #myForm="ngForm" novalidate>
<div class="form-group">
<label for="exampleInput">Username</label>
<input name="username" [(ngModel)]="myModel.username" required
type="input" class="form-control" id="exampleInput" placeholder="Username">
</div>
</form>
To help visualize the state of the form, we will dump myForm
into our template using the json
pipe. The value of the form model and the validity of the form model are separated into two properties and so we need to bind myForm.value
and myForm.valid
to see them both.
<pre class="highlight">{{myForm.value | json}}</pre>
<pre class="highlight">{{myForm.valid | json}}</pre>
If we deleted everything in the username
input, myForm.valid
goes from true
to false
which we can then use to perform additional logic. We could, for instance, disable a submit button when the form is in an invalid state which would look something like the code below.
<button type="submit" [disabled]="!myForm.valid" class="btn btn-primary">Submit</button>
We can also use ngModel
to bind to additional controls such as select
, radio
and checkbox
which you can see in the template below.
<div class="row">
<div class="col-sm-6">
<form #myForm="ngForm" novalidate>
<div class="form-group">
<label for="exampleInput">Username</label>
<input name="usernameManual" [ngModel]="myModel.username" (ngModelChange)="myModel.username = $event" required type="input" class="form-control" id="exampleInput" placeholder="Username">
</div>
<div class="form-group">
<label for="exampleInput">Username</label>
<input name="username" [(ngModel)]="myModel.username" required type="input" class="form-control" id="exampleInput" placeholder="Username">
</div>
<div class="form-group">
<label for="exampleSelect1">Example select</label>
<select name="selectedItem" [(ngModel)]="myModel.selectedItem" class="form-control" id="exampleSelect1">
<option *ngFor="let item of myModel.items" [ngValue]="item">{{item.label}}</option>
</select>
</div>
<fieldset class="form-group">
<legend>Radio buttons</legend>
<div class="form-check">
<label class="form-check-label">
<input type="radio" name="selectedColor" class="form-check-input" [(ngModel)]="myModel.selectedColor" value="red"> Red
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" name="selectedColor" class="form-check-input" [(ngModel)]="myModel.selectedColor" value="green"> Green
</label>
</div>
<div class="form-check">
<label class="form-check-label">
<input type="radio" name="selectedColor" class="form-check-input" [(ngModel)]="myModel.selectedColor" value="blue"> Blue
</label>
</div>
</fieldset>
<div class="form-check">
<label class="form-check-label">
<input type="checkbox" name="isChecked" [(ngModel)]="myModel.isChecked" class="form-check-input"> Check me out
</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
<div class="col-sm-6">
<pre class="highlight"><raw>{{myModel | json}}</pre>
<pre class="highlight"><raw>{{myForm.value | json}}</pre>
<pre class="highlight"><raw>{{myForm.valid | json}}</pre>
</div>
</div>