Lesson Tuesday

In the last two lessons, we learned about two kinds of one-way data binding: event binding and property binding. This lesson will explore how we can combine these to create two-way data binding.

We'll explore what two-way data binding is, how it works, and how best to use it. Then we'll implement two-way data binding to create an edit form that immediately updates our list of to dos.

Two-Way Data Binding

As the name suggests, actions that occur as a result of two-way binding involve information flowing two different ways. What does this mean? Well, event binding is a type of one-way data binding; ts flow of data looks like this:

one-way-data-binding-diagram

Property binding is also a type of one-way data binding but its flow of data goes in the opposite direction:

property-binding-diagram

Two-way data binding combines both event binding and property binding into a single action. This allows us to dynamically alter template values and properties when certain events occur. Here's how the process looks:

angular-two-way-data-binding-diagram

The same event binding steps still occur, but once the template statement is evaluated, data then flows back in the other direction. Angular updates the same template whose event binding triggered this process. This allows us to display data, trigger an action to update that data, and see these changes automatically reflected in the DOM.

Two-Way Bindings and Form Data

Two-way data binding is most commonly used with forms. As the user edits or inputs information into a form field, the template automatically updates to display what the user has added. Let's explore how this works.

Let's implement some two-way binding in our application to demonstrate how it works. Add the following code to the beginning of app.component.html:

app/app.component.html
<h1>Welcome, {{userName}}!</h1>

<form>
  <input [(ngModel)]="userName" type="text" name="user">
</form>

Most of this code should look familiar. We have a dynamic variable userName. We have a basic form with a single field. However, we've also added [(ngModel)]. The [()] is commonly known as banana in a box syntax because it looks like exactly that. Combining both [] and () indicates we're using both event binding and property binding. Angular will listen for an input event which will in turn trigger a change to the value of userName. (Note the name attribute must also be set or the application will throw an error. In the case above, the value of name is arbitrary.)

If you refresh the page, there will be an error in the console. That's because ngModel isn't part of the core functionality we've included in our root module. ngModel's functionality is part of the FormsModule, so we'll need to add that to our root module:

app/app.module.ts
...
import { FormsModule }  from '@angular/forms';

@NgModule({
  ...
  imports: [
    ...
    FormsModule
  ],
})
...

First we import the FormsModule and then we declare it in the imports array. (You may wonder why Angular CLI doesn't automatically include this module. Well, not all applications will use forms and we shouldn't bloat our code with unnecessary dependencies.)

Go ahead and refresh the page. If you enter a name into the form field, the DOM will automatically update as you enter or remove each letter. This is the power of two-way binding.

It may not seem like data is flowing both ways here because we didn't add a method to our class declaration. However, we've added an event binding that recognizes an input and then triggers a property binding which in turn modifies the DOM.

To recap, two-way data binding is the combination of an event-binding handling the flow of data from the template to the back-end of the component and a property binding managing the process of updating an HTML property on the template after this action has occurred.

Creating a Dynamic Edit Form

Now that we've walked through the basics of two-way binding and the ngModel directive, let's use these concepts to create a dynamic edit form in our application.

First we need to let Angular know which Task is currently selected so it knows which one should be edited. We can add a property to our class to keep track of this:

app/app.component.ts
...
export class AppComponent {
  ...
  selectedTask: Task = this.tasks[0];

  editTask() {
    alert("You just requested to edit a Task!");
  }
}

We define a selectedTask property, declare that it must be a Task, and set it to the first Task in our tasks array.

Let's add the following code to the end of our HTML template:

app/app.component.html
<h3>{{selectedTask.description}}</h3>
<p>Task Complete? {{selectedTask.done}}</p>
<h3>Edit Task</h3>
<label>Enter Task Description:</label>
<input [(ngModel)]="selectedTask.description">
<label>Enter Task Priority (1-3):</label>
<br>
<input type="radio" [(ngModel)]="selectedTask.priority" value="1">1 (Low Priority)<br>
<input type="radio" [(ngModel)]="selectedTask.priority" value="2">2 (Medium Priority)<br>
<input type="radio" [(ngModel)]="selectedTask.priority" value="3">3 (High Priority)
...

Let's take a closer look at our form. We've added an <input> field where the user can update the Task's description. It looks like this: input [(ngModel)]="selectedTask.description">.

When the user begins inputting text into this form field, the description property of the selectedTask) will be updated and the changes will be reflected in the DOM immediately.

We also include three radio buttons to update the Task's priority attribute. You'll notice that in addition to including [(ngModel)]="selectedTask.priority", they also have a value attribute (value="1"). Using radio buttons with ngModel is a bit of a special circumstance. We cannot assign the value property of each radio button to the current priority property of the selectedTask because HTML radio buttons require predefined values to select from. We need to include an additional property binding to set the available values a user can choose.

Note: For more information on using ngModel in conjunction with non-text form fields, check out this blog post: "How to Deal with Different Form Controls in Angular 2".

If we refresh our page, we should see our new edit form.

to-do-list-with-edit-form

If you update the form now, it will automatically update the DOM.

However, we can only edit the first task so far. Let's update the code so our edit button works as intended. We'll use the editTask() method to do that.

app/app.component.ts
...
export class AppComponent {
  ...

  editTask(clickedTask) {
    this.selectedTask = clickedTask;
  }

}
...

editTask(clickedTask) now takes the specific Task the user selects as an argument. It updates the selectedTask whenever the event binding is activated.

Now we need to update our HTML template so our updated method takes an argument:

app/app.component.html
...
<ul>
  <li [class]="priorityColor(currentTask)" *ngFor="let currentTask of tasks">{{currentTask.description}} <button (click)="editTask(currentTask)">Edit!</button></li>
</ul>
...

When we refresh the page, we can click a Task and see our edit form update automatically.

Use two-way binding and the ngModel directive in all your edit forms moving forward. In the next lesson, we'll clean up our UI further by learning new directives to dynamically hide and show areas of our template.