Skip to content

Bi-directional binding

The expected state of the application after the "Routing" section is finished: [get code]

With bi-directional data binding, Angular not only watches for changes but also tracks changes that are made by the user (e.g. using input elements such as form fields) and updates the variables accordingly. You'll see for yourself how this works, but first let's generate a new component.

Student editing component

Routing makes more sense when there are multiple views. For now, there is only a student list view. So let's create a new component that will present the fields to be edited by a single student. Run the following command:

ng generate component studentEdit

We have to exchange the generated TypeScript code in the file student-edit.component.ts for the one below:

import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

import { Student } from '../student';
import { StudentService } from '../student.service';

@Component({
  selector: 'app-student-edit',
  templateUrl: './student-edit.component.html',
  styleUrls: [ './student-edit.component.css' ]
})
export class StudentEditComponent implements OnInit {
  student: Student;

  constructor(
    private route: ActivatedRoute,
    private studentService: StudentService,
    private location: Location
  ) {}

  ngOnInit(): void {
    this.getStudent();
  }

  getStudent(): void {
    // Obtaining the value of the "id" parameter and converting it to a number
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Unary_plus
    const id = +this.route.snapshot.paramMap.get('id');
    this.studentService.getStudent(id)
      .subscribe(student => this.student = student);
  }

  goBack(): void {
    this.location.back();
  }

  save(): void {
    // Saves the data and redirects to the previous view
    this.studentService.updateStudent(this.student)
      .subscribe(() => this.goBack());
  }
}

Let's take a look at the elements injected into the constructor:

  • ActivatedRoute stores route information to this instance, ie toStudentEditComponent. This component is especially interested in the route parameters taken from the URL, and we want to get the value of the parameter id, which is the student's identification number.
  • StudentService retrieves student data from a remote server.
  • Location is Angular's service for interacting with the browser. We use it to return to the view that the user was previously in.

Let's replace the generated HTML for the new component with the following:

<section *ngIf="student">
  <h2>Editing #{{student.id}} <i>{{student.name}}</i></h2>

  <div>
    <label for="name">Name and surname:</label>
    <input type="text" id="name" [(ngModel)]="student.name">
  </div>

  <div>
    <label for="email">E-mail address:</label>
    <input type="email" id="email" [(ngModel)]="student.email">
  </div>

  <div>
    <button (click)="goBack()" class="button">Cancel</button>
    <button (click)="save()" class="button button-green">Save</button>
  </div>
</section>

NgModel

The code above has a strange "attribute" on the text fields, right? [(ngModel)] is the two-way data-binding syntax in Angular. In this case, it binds, inter alia, a property student.name with an HTML text field so that data is exchanged in two directions: from thestudent.name property to the text field and from the text field back to student.name. This way, as soon as you make a change to the text box, you'll notice the change in the title above the form.

But that's not all. The <section> element has the condition *ngIf="student". It means that this section will appear only when the service downloads the student data from the server, that is, when the student object is not empty.

Modification of the main module

If the application was launched after previous changes, it may have shown an error. Well, for the correct operation of the bidirectional binding, which we introduced in the code of the editing component through the [(ngModel)] in the text field, you need an additional angular module - FormsModule.

Let's open the file src/app/app.module.ts and import it:

import { FormsModule } from '@angular/forms';

Then add it to the imports array inside the@NgModule decorator:

imports: [
  BrowserModule,
  HttpClientModule,
  AppRoutingModule,
  FormsModule
],

Routing modification

To go to the student edition, router needs a suitable route. So we will import our new component into app-routing.module.ts:

import { StudentEditComponent } from './student-edit/student-edit.component';

However, in the router paths themselves, let's add the following:

{ path: 'edit/:id', component: StudentEditComponent }

Modification of the student list component

The list of students lacks an appropriate hyperlink to be able to edit a specific student. Let's replace the edit button code with:

<a routerLink="/edit/{{student.id}}" class="button button-green">Edytuj</a>

Applying the routerLink directive to an element in a template causes that element to become the link that initiates navigation to the appropriate router route. This link opens one or more components in <router-outlet> on the page.