Observable is introduced since Angular 2 and it’s not new if you are already familiar with async programming. It’s always good idea to learn the basics and you can read this article to understand how Observable works. Today, I wanted to give you an example of Observable that I used in my portal built with Angular 5. Let’s look at how an address book is fetched and displayed in browser using Observable. You can find the live demo at azure.aspnet4you.com.
Let’s breakdown the tasks into 1-2-3!
(1) Service: We need a service to make http/s call to api action that will return an Observable.
(2) Component: We need a component to invoke the service method and Subscribe to the Observable. An instance variable will receive the data when api push the data.
(3) View: This is the final part to bind the component to HTML.
address-book.service.ts:
import { Injectable } from '@angular/core'; import { Address } from './address'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { environment } from '../environments/environment'; import { Observable } from 'rxjs/Rx'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import { CookieService } from 'ngx-cookie-service'; var Addresses2: Address[]; @Injectable() export class AddressBookService { errorMsg: string; successMsg: string; static goodMsg: string = "Successfully retrieved addresses."; static badMsg: string = "Server error: Something went wrong, can not get the data. May be you have not logged in! Look at the console for details."; constructor(private http: HttpClient, private cookieService: CookieService) { } private baseAddressBookUrl = environment.apiDomain + '/api/addressbook/'; getAddresses(isProtected: boolean = false): Observable<Address[]>{ // set the headers required to pass CORS. Don't forget to add the origins at the api (server)! var token = this.cookieService.get("access_token"); let headers = new HttpHeaders().set('Access-Control-Allow-Origin', '*') .append('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') .append('Access-Control-Allow-Headers', 'Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization') .append('Access-Control-Allow-Credentials', 'true'); if (token != "undefined" &&token !="" && token != null) { headers = headers.append('Authorization', 'Bearer ' +token); } let actionName = "GetAllAddresses"; if (isProtected) { actionName = "GetProtectedAddresses"; } return this.http.get(this.baseAddressBookUrl + actionName, { headers}) .map((data: any) => { this.successMsg = AddressBookService.goodMsg; console.log(AddressBookService.goodMsg + " @" + actionName); return data; }) //...errors if any .catch((error: any) => { this.errorMsg = AddressBookService.badMsg; console.log(error || AddressBookService.badMsg); return Observable.throw(AddressBookService.badMsg) }); } getAddress(id: number): Observable<Address> { var token = this.cookieService.get("access_token"); let headers = new HttpHeaders().set('Access-Control-Allow-Origin', '*') .append('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') .append('Access-Control-Allow-Headers', 'Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization') .append('Access-Control-Allow-Credentials', 'true'); if (token != "undefined" && token != "" && token != null) { headers = headers.append('Authorization', 'Bearer ' + token); } let actionName = "GetAddressById/Id?Id="; return this.http.get(this.baseAddressBookUrl + actionName +id, { headers }) .map((data: any) => { this.successMsg = AddressBookService.goodMsg; console.log(AddressBookService.goodMsg + " @" + actionName); return data; }) //...errors if any .catch((error: any) => { this.errorMsg = AddressBookService.badMsg; console.log(error || AddressBookService.badMsg); return Observable.throw(error || AddressBookService.badMsg); }); } getErrorMsg() { return this.errorMsg; } getSuccessMsge() { return this.successMsg; } }
address-book.component.ts:
AddressBookComponent will subscribe to Observable at ngOnInit but the address (an array) variable will receive the value only when data is available.
import { Component, OnInit, OnChanges, SimpleChanges } from '@angular/core'; import { Observable } from 'rxjs/Rx'; import { Address } from './address'; import { AddressBookService } from './address-book.service'; @Component({ selector: 'app-address-list', templateUrl: './address-list.component.html', providers: [AddressBookService] }) export class AddressBookComponent implements OnInit { addresses: Address[]; selectedAddress: Address; title: string = "Prodip's Angular AddressBook"; constructor(private service: AddressBookService) { } ngOnInit() { this.service.getAddresses(false) .subscribe( addrs => this.addresses = addrs, //Bind to view err => { // Log errors if any console.log(err); }); } // Need for events selectAddress(address: Address) { this.selectedAddress = address; } getErrorMsg() { return this.service.getErrorMsg(); } getSuccessMsge() { return this.service.getSuccessMsge(); } }
address.ts:
Structure of Address class-
export class Address { public name: string; public phone: string; public city: string; }
address-list.component.html:
Notice that *ngIf=”addresses” is used to check if address is populated. You don’t have to do anything and addresses will show up in browser if/when address is available!
<p> <h2>{{title}}</h2> <p class="error">{{getErrorMsg()}}</p> <div style="width: 100%; display: table;"> <div style="display: table-row"> <div style="width: 350px; display: table-cell;"> <table *ngIf="addresses"> <thead> <tr> <th>Id</th> <th>Name</th> <th>Phone</th> <th>City</th> </tr> </thead> <tbody> <tr *ngFor="let address of addresses" (click)="selectAddress(address)"> <td>{{address.Id}}</td> <td>{{address.Name}}</td> <td>{{address.Phone}}</td> <td>{{address.City}}</td> </tr> </tbody> </table> </div> </div> <div style="display: table-row"> <div style="display: table-cell;"> <div *ngIf="selectedAddress"> <h3>{{selectedAddress.Name}}'s Address Detail</h3> <div class="form-group"> <Label width="100">Id: {{selectedAddress.Id}}</Label> </div> <div class="form-group"> <Label width="100">Name:</Label> <input type="text" class="form-control" [(ngModel)]="selectedAddress.Name"> </div> <div class="form-group"> <Label width="100">Phone:</Label> <input type="text" class="form-control" [(ngModel)]="selectedAddress.Phone"> </div> <div class="form-group"> <Label width="100">City:</Label> <input type="text" class="form-control" [(ngModel)]="selectedAddress.City"> </div> </div> </div> </div> </div>
That’s about it! Don’t forget to include the component and service in the app.module.ts. You can find the complete source codes in GitHub.