What is data binding?

Data binding is simply the process of linking visually displayed data to the component that drives the display. We have already been doing data binding by displaying data from components on the page and allowing the user to affect the application through visual selections.

Interpolation

The easiest databinding to understand is the one-way binding that interpolation gives us. To interpolate, we just write the name of a variable inside of two curly braces someplace in our template. For example:

<h1>Hello, {{name}}!</h1>

This interplates the name from the following controller into the template so that the user will see the data contained in the component.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})
export class HelloComponent implements OnInit {
  name: string = "World";

  constructor() { }

  ngOnInit() {
  }
}

These interpolation variables may be anything in scope to the template. So if you are inside of an *ngFor directive, any local variable declared in the loop is accessible via interpolation. For example:

  <li *ngFor="let person of people" (click)="onSelect(person)">
    <input type="radio" [value]="person.contact.ssn"> {{person.name}}
  </li>

Displays a list of radio buttons with the values inside of the person object, which exists in the array of people. People is the object we are observing on the controller, and Person is a local variable of each element in that array.

Attribute binding

We have also used attribute binding! In fact, we used it above but did not note it. We bind from the component to the template using square brackets on attributes, classes, and styles. The value on the right-hand side of the equal sign is a variable in scope.

  <li *ngFor="let person of people" (click)="onSelect(person)">
    <input type="radio" [value]="person.contact.ssn"> {{person.name}}
  </li>

In this code, the value associated with each input radio button is bound to person.contact.name.

Here is another example:

<app-profile [person]="selectedPerson"></app-profile>

In this code, the person attribute is bound to selectedPerson, which exists on the comopenent object.

Style and Class binding

We can programmatically change CSS styles and classes via data binding as well. These still use square brackets, but bind to specific attributes on the HTML tag.

For class binding:

<h1 [class.isAdmin]="isAdmin">Hello, {{name}}!</h1>

In this case, the controller needs to have an isAdmin property that gives a boolean value:

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})
export class HelloComponent implements OnInit {
  name: string = "World";
  isAdmin: boolean = false;

  constructor() { }

  ngOnInit() {
  }
}

For style binding:

<h1 [style.color]="colorValue">Hello, World!</h1>
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-hello',
  templateUrl: './hello.component.html',
  styleUrls: ['./hello.component.css']
})
export class HelloComponent implements OnInit {
  colorValue = "blue";

  constructor() { }

  ngOnInit() {
  }
}

Event binding

The previous bindings were all sending data one-way to the template. We can also bind events. We have already used this form of binding to allow elements to be clickable using the (click) event binding. We simply set the (click) attribute equal to the method on the component we wish to run in response to the event:

<button type="button" class="btn btn-secondary" [class.invisible]="editing" (click)="onEditClick()">Edit</button>

Then on the component:

import { Component, OnInit, Input } from '@angular/core';
import { Person } from '../../models/person';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
  @Input() person: Person;
  editing: boolean = false;

  constructor() { }

  ngOnInit() {
  }

  onEditClick() {
	  console.log("Edit mode");
	  this.editing = true;
  }

  save() {
	  this.editing = false;
  }
}

Two-way binding (ngModel)

Two-way databinding is also possible. This is used for form elements to update values behind the form and vice versa. It is indicated by both square brackets and parenthesis for ngModel: [(ngModel)]="someAttribute"

We can two-way bind any property with the [()] syntax, but most of the time, this is for ngModel.

For example:

@Component({
  selector: 'app-two-way',
  template: `
    <input [(ngModel)]="inputText">
    <p>{{inputText}}</p>`
})
export class TwoWayComponent {
  inputText: string = "empty";
}

When the user changes the text in the input box, they will see the text below the box update with each keystroke.

A more complete example

profile.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { Person } from '../../models/person';

@Component({
  selector: 'app-profile',
  templateUrl: './profile.component.html',
  styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
  @Input() person: Person; (0)
  editing: boolean = false; (1)

  constructor() { }

  ngOnInit() {
  }

  onEditClick() { (2)
	  console.log("Edit mode");
	  this.editing = true;
  }

  save() { (3)
	  this.editing = false;
  }
}
1 An input parameter which is databound by the parent component
2 A property of this class which we will bind to show or hide editable elements
3 An event response to clicking the edit button
4 An event response to clicking the save button
profile.component.html
<div class="profile contianer" *ngIf="person">
	<div class="row">
		<div class="col-2">
	<app-profile-photo [(urls)]="person.photos" [alt]="person.name">Photo goes here</app-profile-photo>


		<div class="row justify-content-center">
			<button type="button" class="btn btn-secondary" [class.invisible]="editing" (click)="onEditClick()">Edit</button> (0)
		</div>

		</div>

		<div class="col-10" *ngIf="!editing">

			<div>{{person.name}} - {{person.age}}</div> (1)

			<div>Phone: {{person.contact.phone}}</div>
			<div>Email: <a href="mailto:{{person.contact.email}}">{{person.contact.email}}</a></div>
			<div>SSN: {{person.contact.ssn}}</div>

		</div>

		<div class="col-10" *ngIf="editing">

			<div><input [(ngModel)]="person.name"> - <input [(ngModel)]="person.age"></div> (2)

			<div>Phone: <input [(ngModel)]="person.contact.phone"></div>
			<div>Email: <input [(ngModel)]="person.contact.email"></div>
			<div>SSN: {{person.contact.ssn}}</div>

			<button class="btn btn-primary" (click)="save()">Save</button> (3)

		</div>

<div class="profile" *ngIf="!person">
	No person selected.
</div>
1 Class binding
2 Interpolation
3 Two-way binding
4 Event binding

Questions

  • Could we condense the edit/save button handlers into one handler?

  • Do we need those methods at all?

  • Are the form types we use here the best form types for the data?

  • Should the editable form be a separate component?

    • Yes. We will look at reactive forms next time.