Angular 2 / TypeScript / Visual Studio Code – Tutorial

  Angular, TypeScript, Visual Studio Code

Using a datasource with an Angular 2 service

We need to get rid of this embarrassingly mocked customer data. If mocking, then with pride. The Angular 2 mechanism to provide data to components is called Service. Such a service usually works asynchronous, because the response time of a remote data source like a database can vary quite a bit. And in order to not mess up our UI while the service is still waiting for data, we use promises. Read more about promises here: http://exploringjs.com/es6/ch_promises.html.

import { Injectable } from '@angular/core';
import { Customer } from './customer';

@Injectable()
export class CustomerService {
  getCustomers(): Promise<Customer[]> {
    return Promise.resolve(CUSTOMERS);
  }
}
const CUSTOMERS = [
 {
 "id": "1",
 "name": "Houston",
 "firstname": "John",
 "street": "Bakerstreet 56",
 "zipcode": 45332,
 "city": "New York",
 "country": "United States",
 "phone": "+1 456 34342930",
 "email": "jhouston@yahoo.com",
 "age": "67"
 },
 {
 "id": "2",
 "name": "Smith",
 "firstname": "Ken",
 "street": "Wakersfield Road 22",
 "zipcode": 455344,
 "city": "Boston",
 "country": "United States",
 "phone": "+1 665 9457734",
 "email": "ksmith@google.com",
 "age": "38"
 }
]

Three things should be noted about the above code. First, we used the @Injectable() decorator which collects metadata about this service to be used later in the component which injects this service. Second, we now employ a lifecycle method ngOnInit() which requires the OnInit interface. Third, we still use the constant CUSTOMERS which we cut out of cust-list.component.ts and put it here. This is just for testing. We will get rid of it soon.

We now know what to do in our customer list component. We need to remove the CUSTOMERS mock data, inject the new CustomerService somehow, and create a getCustomers() method which receives the promise that is provided by our customer’s service method getCustomers().

import { Component } from '@angular/core';
import { Customer } from './customer';
import { CustomerService } from './customer.service';

@Component({
 selector: 'master-detail',
 template: `
 <div id="left">
 <table class="table">
 <thead>
 <tr>
 <th>ID</th>
 <th>Name</th>
 <th>Firstname</th>
 </tr>
 </thead>
 <tbody>
 <tr *ngFor="let customery of customers" class="row" [class.selected]="customery === selectedCustomer" (click)="onSelect(customery)">
 <td>{{customery.id}}</td><td>{{customery.name}}</td>{{customery.firstname}}
 </tr>
 </tbody>
 </table>
 </div>
 <div id="right">
 <customer-detail [customer]="selectedCustomer">Loading...</customer-detail>
 </div>
`,
providers: [ CustomerService ]
})

export class CustListComponent {
  constructor(private customerService: CustomerService) { }
  customers: Customer[];
  selectedCustomer: Customer;
  onSelect(customer: Customer): void {
    this.selectedCustomer = customer;
  }
  ngOnInit(): void {
    this.getCustomers();
  }
  getCustomers(): void {
    this.customerService.getCustomers().then(customers => this.customers = customers);
  }
}

The last property in our @Component() decorator is new. Without it, you would get a No provider for CustomerService! exception. It corresponds to the argument in the component’s constructor, which makes is available to the class. One important remark to the constructor: The constructor is for simple initializations like wiring constructor parameters to properties. It’s not for heavy lifting. We should be able to create a component in a test and not worry that it might do real work — like calling a server! — before we tell it to do so.

In the last line, which acts on the promise using then, we see a new operator called the arrow function. Read this for more information about it: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions. What this line does is acting on all elements of the returned value, which is only one thing: the customers array. It is assigned to the instance variable customers once and that’s it. So why did we use an arraow function expression in the first place? Well, its a convenient way of handling this. If we resolved the arrow function with an anonymous callback function, this would not be referencing our component object. See how you could resolve the arrow function:

getCustomers(): void {
 var that = this;
 this.customerService.getCustomers().then(function(customers) { that.customers = customers });
 }

We’ll stick to the arrow function expression.

If everything works so far, we still see our customer list and we can hover an click its items. Now the time has come to get rid of the constant customer data array. We want to access a real data source, over HTTPS, use a REST API, and implement some decent error handling. First of all, we need to add the Angular2 Http module to the code in app.module.ts:

import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { CustListComponent } from './cust-list.component';
import { CustDetailComponent } from './cust-detail.component';

@NgModule({
  imports: [ BrowserModule, HttpModule ],
  declarations: [ AppComponent, CustListComponent, CustDetailComponent ],
  bootstrap: [ AppComponent, CustListComponent ]
})
export class AppModule { }

There is not much difference to what we had before. We’ve added the HttpModule import and put it into the imports array of the @NgModule decorator. Putting the required functionality into the customer service is a little bit more complex. Because the data service is seperated from the cust-list component’s functionality, we can just change the service and don’t need to worry about breaking things elsewhere. Now that the tension is at its peak, see the new customer.service.ts code:

import { Injectable, OnInit } from '@angular/core';
import { Customer } from './customer';
import { Headers, Http } from '@angular/http';
import 'rxjs/add/operator/toPromise';

@Injectable()
export class CustomerService {
  private headers = new Headers({'Content-Type': 'application/json'});
  private walkthroughUrl = 'https://walkthrough.apispark.net/v1/customers'; // URL to REST api

 constructor(private http: Http) { }

 getCustomers(): Promise<Customer[]> {
   return this.http.get(this.walkthroughUrl)
       .toPromise()
       .then(function(response) { return response.json(); })
       .catch(this.handleError);
  }
  private handleError(error: any): Promise<any> {
    console.error('An error occurred. ', error); 
    return Promise.reject(error.message || error);
  }
}

What’s really worth mentioning here is that http.get returns an RxJs compatible Observable object. The cust-list.component expects a Promise. We definitely don’t want to change all components which use the customer.service (so far we have only one, but it could have been much more). Therefore, we import the toPromise code file and insert it into the method chain so that the Observable is transformed into a Promise.

 

 

LEAVE A COMMENT