Angular 5 HTTP using Obvservables – Part 3
WRITTEN BY GARETH DUNNE @JSDIARIES
In this third part of this reactive data series we will be focusing on displaying the data results from our HTTP call in our view component.
While part 1 and 2 felt like setup processes we will now get to see how our data will interact with our view layer.
Again, if your looking to expand your Angular and Typescript knowledge I highly recommend this Angular & TypeScript book by Yakov Fain.
This tutorial series is based on my application Beer Name Finder which you can view here:
.
If you enjoy this dashboard, please consider giving it an upvote on Product Hunt here.
Linking the Component to the HTTP Request
In order for our home.component.ts
file to have access to our beer HTTP methods we have to inject our beer.service.ts
into it.
Simply import this service along with the required imports for using Rxjs Observable and Subscriptions. These Rxjs imports will be a key factor in making our data reactive.
We will also import our Beer type definition model that will map all the appropriate properties from our data call.
import { Component, OnInit } from '@angular/core'; import { BeerService } from '../beer.service'; import { Beer } from '../beer'; import { routerTransition, moveInLeft, Bounce } from '../animations' import 'rxjs/add/operator/publish' import { Subscription } from 'rxjs/Subscription' export class HomeComponent implements OnInit { constructor(private _beerService: BeerService) { this.requestComplete = false; } ngOnInit() { this.getBeers(); this.listenForBeerStream(); } listenForBeerStream() { } getBeers() { }
We have our ngOnInit()
lifecycle hook that comes as standard when you generate a component through the Angular CLI.
Inside this we then invoke two methods one after another in order to separate our logic for retrieve and displaying the beers. We will include this logic as I explain it.
listenForBeerStream() { this._beerService.beerAnnounced$.subscribe( beers => { let beerStream = new Array(); for (let key in beers) { if (beers.hasOwnProperty(key)) { beerStream.push(beers[key]); } } this.beers = beerStream.slice(); } ) }
There are a lot of things going on here so first of all, remember the beerAnnounced$
variable that we created in our service in part 2?
It looked like this:
beerAnnouncedSource = new Subject(); beerAnnounced$ = this.beerAnnouncedSource.asObservable();
Well, beerAnnouncedSource
is a list of type Beers that are also Subjects. Without getting into the complexities of the Observable pattern think of a Subject as a open channel of data that will announce itself to any of our components that have specifically subscribed to it.
There are plenty of other ways to make reactive component data using Rxjs however using a Subject allows us to produce a hot observable of data. In otherwords, you could say this is a constant stream of data.
On top of this, a Subject inherits the same methods that an Observable and shares similar functionality but a Subject has a state and it keeps a list of observers.
So we have access to the beerAnnouncedSource
variable through our beer services. We then invoke .subscribe()
on it in order access its data.
We then use ES6 arrow filter functionality =>
and push the results in to a new local array variable.
We will only get the benefit of having a reactive Subject by having changeable data. You might be asking why is it being used for one API call? Well further down the line we will be using a search functionality where you can type into an input field and the Rxjs variable will get refresh as you are typing. This gives a feeling of a live auto populating search.
Reso
We can now import our Beer class to any of our components to reference data retrieved from the Beer API. So our home.component.ts
file should look like this:
import { Component, OnInit , ElementRef, NgZone} from '@angular/core'; import { BeerService } from '../beer.service'; import { Beer } from '../beer'; import { routerTransition, moveInLeft } from '../animations' import { Injectable } from '@angular/core'; import 'rxjs/add/operator/publish'; import { Ng4LoadingSpinnerService } from 'ng4-loading-spinner'; import { Subscription } from 'rxjs/Subscription' @Injectable() @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], animations: [routerTransition, moveInLeft], host: { '[@routerTransition]': '' }, }) export class HomeComponent implements OnInit { beers: Beer[]; categories: any; ageCheck: string; searchEnabled:boolean; errorMsg: String; alreadyChecked : boolean; constructor(private _beerService: BeerService, private _spinerService: Ng4LoadingSpinnerService, private seo: SeoService, private zone:NgZone, ) { this.check = "dgdfgdf" this.alreadyChecked = this._beerService.ageChecked; } ngOnInit() { this.searchEnabled = this._beerService.searchEnabled; if(!this.searchEnabled) this.getBeers(); this.listenForBeerStream(); } listenForBeerStream() { this._beerService.beerAnnounced$.subscribe( beers => { let beerStream = new Array(); for (let key in beers) { if (beers.hasOwnProperty(key)) { beerStream.push(beers[key]); } } this.beers = beerStream.slice() } ) } getBeers() { this._spinerService.show() var obsBeers = this._beerService.getBeers(); var hot = obsBeers.publish(); obsBeers.subscribe((res) =>{ this.beers = res.data; this._spinerService.hide(); error => { this.errorMsg = error } } ) hot.connect(); } }
In the getBeers()
method we show our ngspinner which is detailed in the next section below.
We call our get beers method from the beer service:
var obsBeers = this._beerService.getBeers();
We publish this to data so that it can become a ConnectableObservable
:
var hot = obsBeers.publish();
This means that its only a reference to the source data.
We need to subscribe to the data and map it to the local array that was initialised at the start of the file of type Beer
beers: Beer[];
obsBeers.subscribe((res) =>{ this.beers = res.data; this._spinerService.hide(); error => { this.errorMsg = error } }
And possibly the most important thing to note here is the this line:
hot.connect();
This allows our hot variable to received data emitted from the Observable is was only previously just referencing.
While I admit the whole Rxjs Observable pattern can be complicated to wrap your head around initially. When its broken down into the idea that publisher send data and subscribers receive it. Everything else in between can be thought of as methods that tweak the way that data is emitted and who receives it.
So with all this data readily available we just have to map it to our UI.
Mapping Data to UI
In our home.component.html
file we need iterate through the beer data and apply it to our user interface.
So we are just using some Angular directives to create markup from the beer data.
We check if our beer array exists:
*ngIf="beers"
UI Loading
Having an indicator for loading is optional but provides a nice bit of user experience as your HTTP is working itself out. I have found a nice little library that will display a spinner for all your HTTP requests. Check it out here. In your app.component.html
file insert the element that references the loader like so:
And in your service you need to import :
import {HttpInterceptorService } from 'ng-http-loader/http-interceptor.service';
and replace all instances of Http
with HttpInterceptorService
constructor( private _http: Http) { }
constructor( private _http: HttpInterceptorService) { }
A loader nicely styled loader will appear whenever making a HTTP call from anywhere in your app.