Blog

- January 06, 2016

In the first part of the article, we saw how to start with Angular 2 to be able to display a list of elements. This list was hard-coded and it’s time to go furter to show how to use more advanced features like services and HTTP to provide a real-world application.

Leveraging a service for data

The implementation of the selectCompany method should externalize processing into a dedicated service. Like with version 1, Angular 2 allows us to implement services and inject them into other parts of your application.

An Angular 2 service simply consists of a class annotated with Injectable. In the code below, we create the CompanyService service that can return a list of Company class.

import {Injectable} from 'angular2/core';

export class Company {
  constructor(public id: number, public name: string) { }
}

@Injectable()
export class CompanyService {
  constructor() {
  }

  getCompanies() {
    return [
      new Company(123, 'Restlet'),
      new Company(124, 'Google')
    ];
  }
}

Later on, we’ll refactor this code to make it retrieve data from a Web API using HTTP. This implies making this operation asynchronously.

The CompanyList simply needs to import the service class and add it within its list of providers. We can then specify a parameter of this type in the constructor of the component and Angular will give us the corresponding instance. Initializing companies is then as simple as calling the getCompanies service. We use the OnInit interface to load the list once the component is loaded.

import { Company, CompanyService } from './company-service';

@Component({
  (...)
  providers: [ CompanyService ]
  (...)
})
export class CompanyList implements OnInit {
  public companies: Company[];

  constructor(private service: CompanyService) {
    this.service = service;
  }

  ngOnInit() {
    this.companies = services.getCompanies();
  }
}

Now let’s see how to display a particular company.

Displaying and editing a particular company

The CompanyDetails component aims at displaying and updating a particular company’s details. Here we obviously need to leverage HTML forms and their relative support in Angular.

First implement a company as previously shown. We need to inject an object of RouteParams type to get the identifier of the company specified when calling the route. Such object has a get method to get values of such attributes.

export class CompanyDetails implements OnInit {
  public company: Company;

  constructor(private service: CompanyService,
         routeParams: RouteParams) {
    this.service = service;
    this.routeParams = routeParams;
  }

  ngOnInit() {
    let id = this.routeParams.get('id');
    this.company = this.service.getCompany(id);
  }
}

Now let’s tackle the associated template. We start here with a simple form containing an input for the company name.

@Component({
  (...)
  template: `
    <div class="container">
      <form>
        <div class="form-group">
          <label for="name">Name</label>
          <input type="text" class="form-control" required>
          <div class="alert alert-danger">Name is required</div>
        </div>
        <button class="btn btn-default">Submit</button>
      </form>
    </div>
  `  
})

Obviously Angular 2 still supports bidirectional binding using the ngModel directive in a similar way as Angular 1. The syntax is provided [(ngModel)] to link the expression to the attribute and handle the event bindings as well. This expression must correspond to the field we want to link the input to.

We need an ngIf to avoid displaying the form (and evaluate expression with company before data is present). The code can be added at the root level of the template: *ngIf="company".

@Component({
  (...)
  template: `
    <div class="container" *ngIf="company">
      <form>
        <div class="form-group">
          <label for="name">Name</label>
          <input [(ngModel)]="company.name" type="text" class="form-control" required>
          <div [hidden]="name.valid" class="alert alert-danger">Name is required</div>
        </div>
        <button class="btn btn-default">Submit</button>
      </form>
    </div>
  `
})

Regarding form validation, Angular 2 still handles a state for the form. To leverage it within the template, we need to define a local variable for ngForm. For each field, we also need to specify a ngControl attribute with the name of the field. It’s now possible to use expressions based on this state like with Angular 1. This allows us to hide error message areas when inputs are valid or to disable the button if the form isn’t globally valid.

@Component({
  (...)
  template: `
    <div class="container" *ngIf="company">
      <form #companyForm="ngForm" (ng-submit)="updateCompany()">
        <div class="form-group">
          <label for="name">Name</label>
          <input [(ngModel)]="company.name" ngControl="name" #name="ngForm" type="text" class="form-control" required>
          <div [hidden]="name.valid" class="alert alert-danger">Name is required</div>
        </div>
        <button [disabled]="!companyForm.form.valid" class="btn btn-default">Submit</button>
      </form>
    </div>
  `
})

The last step is to add the update processing when clicking on the button. Follow the same process than for the event to display company details.

@Component({
  (...)
  template: `
    <div class="container" *ngIf="company">
      <form #companyForm="ngForm" (ng-submit)="updateCompany()">
        <div class="form-group">
          <label for="name">Name</label>
          <input [(ngModel)]="company.name" ngControl="name" #name="ngForm" type="text" class="form-control" required>
          <div [hidden]="name.valid" class="alert alert-danger">Name is required</div>
        </div>
        <button (click)="updateCompany()" [disabled]="!companyForm.form.valid" class="btn btn-default">Submit</button>
      </form>
    </div>
  `
})

It’s time to connect all this to our Web API.

Interacting with the Web API

Where you want to execute HTTP requests, you need to import the Http class and also the Headers class if you want to use additional headers.

import {Http, Headers} from 'angular2/http';
import 'rxjs/add/operator/map';

Now you have imported these classes, you can use them within the service leveraging Angular 2 Dependency Injection. Simply add an object of this type as parameter of the service class and Angular will automatically provide you with a corresponding instance when instantiating the service.

@Injectable()
export class CompanyService {
  constructor(http:Http) {
    this.http = http;
  }
  (...)
}

Angular 2 doesn’t use promises anymore for its HTTP support but leverages observables. The latters are much more powerful and integrate features like lazy initialization and cancelling.

The http instance can now be used to execute HTTP requests. We will first implement a GET method that will retrieve the list of companies from the Web API hosted on APISpark. Since this API requires basic authentication, we will add an Authentication header for this call.

createAuthorizationHeader() {
  var headers = new Headers();
  headers.append('Authorization', 'Basic ' +
         btoa('username:password')); 
  return headers;
}

getCompanies() {
  return this.http.get('https://angular2.apispark.net/v1/companies/', {
    headers: this.createAuthorizationHeader()
  }).map(res => res.json());
}

Since the getCompanies is now asynchronous, you can refactor the use of the getCompanies method from the CompanyList component to get the list of companies from a callback specified using the subscribe method. Notice that the request is actually executed once the subscribe method is called on the corresponding observable. As a matter of fact, Observables are lazy.

export class CompanyList implements OnInit {
  public companies: Company[];

  (...)

  ngOnInit() {
    this.service.getCompanies().subscribe(
      data => this.companies = data);
  }
}

Regarding the updateCompany method, we want to execute a PUT method. In the same way as for the getCompany method, let’s take advantage of the http object and call its PUT method. In this case, we musn’t forget to set the content type in the headers.

updateCompany(id:string, company:Company) {
  var headers = new Headers();
  this.createAuthorizationHeader(headers);
  headers.append('Content-Type', 'application/json');
  return this.http.put(
      'https://angular2.apispark.net/v1/companies/' + id,
      JSON.stringify(company), {
    headers: headers
  });
}

We can now update the updateCompany method of the CompanyDetails component to take into account the new method of the service.

export class CompanyDetails implements OnInit {
  (...)
  updateCompany() {
    let id = this.routeParams.get('id');
    this.service.updateCompany(id, this.company).resolve();
  }
}

Conclusion

As you can see throughout this article, Angular 2 applications are really different from Angular 1 ones and are focused around the concept of components. Angular 2 allows you to write applications with EcmaScript5 only but you also leverage ES6 decorators to define some metadata around components, making their implementation more concise and readable.

Angular 2 provides a new syntax within templates that looks like the one from Angular 1, but it’s more flexible and elegant. It also offers concepts that make version 1 popular like Dependency Injection, form and HTTP support.

A key point is the migration of existing applications to this new version. Several approaches can be considered:

  • The migration from scratch. A new application is entirely rewritten with the new version.
  • An incremental approach. Using ngUpgrade, you can mix both Angular 1 and Angular 2 elements within the same application allowing to migrate services or components one at a time.

The source code is available in the following Github repository: https://github.com/restlet/restlet-sample-angular2.

CTA_Free Trial_2