Wednesday, 27 June 2018

Using HTTP with Firebase in Angular

> Angular 6 Samples

Content Covered

  • Firebase Setup
  • Creating a Service with CLI
  • Service Basic Setup
  • Component and Template Basic Setup
  • Adding Functionality to Perform Post/Put/Get Actions
    • Adjusting Request Headers
    • Sending Get Request to Read Data From Server
    • Changing Service storeServers Method to PUT Request
    • Transform Response with Observable MAP Operator
    • Using the Returned Data
    • Catching Errors
    • Using the "async" Pipe with Http Requests

Environment: MS VS Code, bootstrap 3 and Angular 6

Download Project (zip): SampleHttp

The new HTTP client in Angular 6.

Important: Open servers.service.ts and change the  backEndUrl per your Firebase URL as described under Firebase Setup below.
 

Firebase Setup


We'll use Google Firebase for the database and testing client server functionality with http. Click here to look at the Firebase setup.

Keep a note of your Firebase url

https://testhttp-XXXX.firebaseio.com/

Note that for this test we are not using the new HttpClient.

Creating a Service with CLI


Create a new service as servers.service.ts with the CLI. Make sure to add ServersService to app.module.ts provides array and to import it properly as well.

>ng g s servers

Importing FormsModule and HttpModule into AppModule

We'll also need to import FormsModule and HttpModule and then add to the imports sections.

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [ServersService],
  bootstrap: [AppComponent]
}) 


Service Basic Setup


Inject a Http Service into this newly created service. Make sure to put @Injectable() directive and to import it from the @angular/core. Then inject http service via contrictor. Make sure to import Http from "@angular/http".

Also make  sure to change "backEndUrl" variable with your own URL which you'll need to pick up from Firebase.

Here is the basic setup for the service. We'll add functionality to it later. This service will have the methods to connect to the Firebase and perform actions.

import { Injectable } from "@angular/core";
import { Http } from "@angular/http";

@Injectable()
export class ServersService {

  backEndUrl = "https://testhttp-XXXX.firebaseio.com/";

  constructor(private http: Http) { }

}


Component and Template Basic Setup


Now go to app.components.ts and inject the service. Make sure to import it as well.

constructor(private serversService : ServersService){}

In the app component currently we have servers array which we display on the page.

  servers = [
    {
      name: 'Testserver',
      capacity: 10,
      id: this.generateId()
    },
    {
      name: 'Liveserver',
      capacity: 100,
      id: this.generateId()
    }
  ];


On the template, we display the above array data. We also have a textbox in which user can type in a new server and then click Add Server button to add the server to the array.

<div class="container">
  <div class="row">
    <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
      <input type="text" #serverName>
      <button class="btn btn-primary" (click)="onAddServer(serverName.value)">Add Server</button>
      <hr>
      <ul class="list-group" *ngFor="let server of servers">
        <li class="list-group-item">{{ server.name }} (ID: {{ server.id }})</li>
      </ul>
    </div>
  </div>
</div>

and the onAddServer event in the component looks like

 onAddServer(name: string) {
    this.servers.push({
      name: name,
      capacity: 50,
      id: this.generateId()
    });
  }
 
  private generateId() {
    return Math.round(Math.random() * 10000);
  }


Run this basic setup to see how things are. We are not saving any thing to the Firebase yet.

Adding Functionality to Perform Post/Put/Get/Delete Actions


Now the fun part begins...

Storing Servers


Add a new button to the template

<button class="btn btn-primary" (click)="onSave()">Save Servers</button> 

The onSave event will get to the service and will execute the storeServersMethods. The service is returning an observable so we need to subscribe to it. In the call back we'll get the response and errors.

No need to unsubscribe since there will be only one response and Angular will tke care of it for us.

Service method

 storeServers(servers: any[]){
    //we are sending back the observable which we'll then use in the component.
    return this.http.post(this.backEndUrl+'data.json', servers);
  }

onSave Event

  onSave(){
    this.serversService.storeServers(this.servers)
      .subscribe(
        (response) => console.log(response),
        (error) => console.log(error)
      );
  }

Run the app and and click Save Servers. Take a look at the console and see the response. We should get some thing like

Response {_body: "{"name":"-LG-16xsYwb2C9z2phA6"}", status: 200, ok: true, statusText: "OK", headers: Headers, …}

Take a look at the Firebase, we should see our servers there as well.


Adjusting Request Headers


In the above example we didn't configure any header. We might be needing these. Inorder to send these, we'll need to generate these with Headers object. Make sure to import it as well.

We'll pass key value pairs to to the Header.

We can then add a third argument as javascript object to the post request

 import { Http, Headers } from "@angular/http";

  storeServers(servers: any[]){
    //we are sending back the observable which we'll then use in the component.
    const headers = new Headers({'Content-Type':'application/json'})
    return this.http.post(this.backEndUrl+'data.json', servers,
{headers: headers});
  }


Sending Get Request to Read Data From Server


We are getting the servers back from the server. So in servers service we'll put a new method which will do a GET action and will return an observable.

  getServers(){
    //returns observable
    return this.http.get(this.backEndUrl+'data.json');
  }

In the template, create a new button GetServers

<button class="btn btn-primary" (click)="onGet()">Get Servers</button>

Then in the component, we'll write the onGet() method which will call the getServers method of the service. Since the service is returning an observable, we'll need to subscribe to it. After clicking the button, take a look at the console and there should be some data in there.

  onGet(){
    this.serversService.getServers()
    .subscribe(
      (response) => console.log(response),
      (error) => console.log(error)
    );
  }


We can unwrap the text returned into real javascript object. For this first import Response from http.

import { Response } from '@angular/http';

We'll extract our data from the Response object using response.json. It will extract the data from the body property and turn it into json. Here is updated method. Do take note of response is of type Response being added here.

  onGet(){
    this.serversService.getServers()
    .subscribe(
      (
response: Response) => {
        const data = response.json();
        console.log(data);

      },
      (error) => console.log(error)
    );
  }


After the above change, we should see something like following

{-LG-16xsYwb2C9z2phA6: Array(2)}
-LG-16xsYwb2C9z2phA6:Array(2)
0:{capacity: 10, id: 2712, name: "Testserver"}
1:{capacity: 100, id: 4584, name: "Liveserver"}
length:2


Changing Service storeServers Method to PUT Request


With the put request, Firebase will update existing data.

  storeServers(servers: any[]){
    //we are sending back the observable which we'll then use in the component.
    const headers = new Headers({'Content-Type':'application/json'})
    // return this.http.post(this.backEndUrl+'data.json', servers, {headers: headers});
    return this.http.put(this.backEndUrl+'data.json', servers, {headers: headers});
  }


Click save, console should look like

Response {_body: "[{"capacity":10,"id":6665,"name":"Testserver"},{"capacity":100,"id":8004,"name":"Liveserver"}]", status: 200, ok: true, statusText: "OK", headers: Headers, …}

and then take a look at the firebase


Transform Response with Observable MAP Operator


We'll use map for this. Since we are not using rxjs-compat, the map operator needs to be used as pipe(map(...)). Make sure to import map

import { map } from 'rxjs/operators';

If you are using rxjs-compat, make sure to import map as following and also do not wrap map inside pipe.

import 'rxjs/Rx';

We'll be re-writting the onGet component method and then getServers service method. We'll be still getting back observable, but in this case we'll transform the data.

We'll wrap the map inside pipe. Make to import map as detailed above in the service. In map we get back response which is of type Response. So make sure to import it.

In the function, we are returning the data but map operator will take the data and will wrap it another observable. Here is the new function

  getServers(){
    //map returns observable
    return this.http.get(this.backEndUrl+'data.json')
      .pipe(
        map(
          (response: Response) => {
            const data = response.json();
            //here we are returning the data but map operator will take the data and will wrap in another observable
            return data;
          }
        )
      );
  }


Now in the component onGet method, we are not any more getting back a response instead it will be an array of servers of type any. The new code now looks like

  onGet(){
    this.serversService.getServers()
    .subscribe(
      (servers: any[]) => console.log(servers),
      (error) => console.log(error)
    );
  }


Run the app after this change and take a look at the console.

(2) [{…}, {…}]
0:{capacity: 10, id: 6665, name: "Testserver"}
1:{capacity: 100, id: 8004, name: "Liveserver"}
length:2
__proto__:Array(0)

 

 Using the Returned Data


We are getting the data but haven't been using it so far.

All we have to do is instead of console.log(servers), change to this.servers = servers.

  onGet(){
    this.serversService.getServers()
    .subscribe(
      (servers: any[]) => this.servers = servers,
      (error) => console.log(error)
    );
  }


Catching Errors


Depending upon whether we are using rxjs-compat or not, the operator use will be little different. In this example, we are not using rxjs-compat so

Instead of

    ....catch(error => {
        return Observable.throw(...)
    })


use

    ....pipe(catchError(error => {
        return throwError(...)
    }))


And make sure to import it:

Instead of

import 'rxjs/Rx';

and

import { Observable } from 'rxjs/Observable';

use

import { catchError } from 'rxjs/operators';

and

import { throwError } from 'rxjs';


For this example, go to the service and update getServers from

   return this.http.get(this.backEndUrl+'data.json')

to

   return this.http.get(this.backEndUrl+'data')

For Firebase this will be an invalid request. Run the app and take a look at the console

Failed to load https://console.firebase.google.com/project/testhttp-XXXX/database/data/data: Redirect from 'https://console.firebase.google.com/project/testhttp-XXXX/database/data/data' to 'https://accounts.google.com/ServiceLogin?passive=XXXXX&osid=1&continue=https://console.firebase.google.com/project/testhttp-XXXX/database/data/data&followup=https://console.firebase.google.com/project/testhttp-XXXX/database/data/data' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
app.component.ts:51

Response {_body: ProgressEvent, status: 0, ok: false, statusText: "", headers: Headers, …}
headers:Headers
_headers:Map(0) {}
_normalizedNames:Map(0) {}
__proto__:Object
ok:false
status:0
statusText:""
type:3
url:null
_body:ProgressEvent {isTrusted: true, lengthComputable: false, loaded: 0, total: 0, type: "error", …}
__proto__:Body

We'll now implement the catchError and throw error operators in getServers method of the service. For this do proper imports as detailed above. Since in this example we are not using rxjs-compat, we'll work with catchError and throwError. Also, make sure to wrap the operator inside pipe. Here is the updated code.

  getServers(){
    //map and catchError returns observable
    return this.http.get(this.backEndUrl+'data')
      .pipe(
        map(
          (response: Response) => {
            const data = response.json();
            //here we are returning the data but map operator will take the data and will wrap in another observable
            return data;
          }
        )
      )
      .pipe(
        catchError(
          (error: Response) => {
            //we need to return an observable here as well so use throwError
            console.log(error);
            return throwError(error);
          }
        )
      );
  }


Run the app and take a look at the console. You'll see a full message since the service method is doing console.log;  and will also see the new string message since the component is doing console.log on the received error.

Using the "async" Pipe with Http Requests


We'll be using the async pipe which we learned in the pipe sample.

Follow the following 4 steps in Firebase and create a new value and pick its url.


In the service create a new method called getMyNewTest and get the new MyNewTest

  getMyNewTest(){
    //this will return an observable
    return this.http.get(this.backEndUrl+'MyNewTest.json')
      .pipe(
        map(
          (response: Response) => {
            return response.json();
          }
        )
      );
  }


Go to the component and create a new property. Usually we need to subscribe to an observable but not in this case.

  myNewTest = this.serversService.getMyNewTest();

We want to display MyNewTest in the template. For this go to the template and output the myNewTest.

 <h1>{{ myNewTest }}</h1>

Now run the app and you'll see Object Object.

Since myNewTest is an observable and to unpack we need to subscribe. However, with the async pipe, just go to the template and add pipe async.

 <h1>{{ myNewTest | async }}</h1>

With that in place, run the app and you'll see the value now.