Telerik blogs
AngularT Dark_870x220

Making HTTP requests and dealing with asynchronous code is vital for many applications. In this article, I'll show you how to handle HTTP operations in Angular and how to work with observables.

Angular is a framework for building dynamic client-side applications using HTML, CSS and JavaScript. It has a nice CLI tool that helps with developer productivity and with generating code that follows the recommended Angular design guide so you can build fast, responsive and modular applications. In this article, I’ll show you how to make HTTP requests and work with RxJS observables.

If you want to continue reading, you should already have an understanding of components, directives, modules, data binding, services and dependency injection in Angular. If you don’t know those things, you’re in good company because I’ve written about them 😉😃. Here are the links to the articles I’ve written covering those topics:

  1. A Practical Guide To Angular: Environment and Project Set Up
  2. A Practical Guide To Angular: Components and NgModules
  3. A Practical Guide To Angular: Data Binding & Directives
  4. A Practical Guide To Angular: Services & Dependency Injection

The application we’ll build together while you go through this article builds on the sample application from the articles I listed above. If you have been reading and working along with me over those articles, you should have the complete code. Otherwise, you can download the project on GitHub. When you download it, you should then copy the content from src-part-4 folder into the src folder if you want to code along while you read.

Setting up the JSON Data

Before we proceed with making an HTTP request, we should set up a server which should know how to respond to HTTP requests and return the right data. However, we won’t be creating any server. We will create a JSON file and configure the app so that the Angular CLI can serve the file when it’s requested.

To create this JSON file, add a new file src/api/expenses.json with the content below in it.

[
  {
    "description": "First shopping for the month",
    "amount": 20,
    "date": "2019-08-12"
  },
  {
    "description": "Bicycle for Amy",
    "amount": 10,
    "date": "2019-08-08"
  },
  {
    "description": "First shopping for the month",
    "amount": 14,
    "date": "2019-07-12"
  },
  {
    "description": "First shopping for the month",
    "amount": 20,
    "date": "2019-06-12"
  },
  {
    "description": "Second shopping for the month",
    "amount": 25,
    "date": "2019-06-22"
  },
  {
    "description": "Second shopping for the month",
    "amount": 19,
    "date": "2019-08-24"
  },
  {
    "description": "Beach trip",
    "amount": 210,
    "date": "2019-08-03"
  },
  {
    "description": "Gajeel's Party",
    "amount": 102,
    "date": "2019-07-22"
  },
  {
    "description": "Scooter",
    "amount": 310,
    "date": "2019-06-19"
  }
]

Then we need to configure the project’s build setting in angular.json so it serves the newly added JSON file when it’s requested. Open angular.json, go to line 44 and update the array as follows:

"assets": ["src/favicon.ico", "src/assets", "src/api"],

This array contains assets for the application, which will be served by the development server when you start the application. We just added src/api to the list. Whatever file that’s added there will be served from locahost:PORT/api/. The expenses.json file we added will be gotten from locahost:PORT/api/expenses.json.

Working with the HttpClient Service

Many browsers support two APIs for making HTTP requests — the XMLHttpRequest and the Fetch API. Angular provides the HttpClient service as a way to communicate with remote servers over HTTP. The HttpClient provides a simple-to-use API for working with HTTP and relies on the XMLHttpRequest API. It gives you the ability to intercept the request and response, work with Observable APIs, use typed request and response objects, retry failed requests, and test code that relies on HTTP.

The HttpClient service is registered in the HttpClientModule. To use this service, we have to import the necessary module from @angular/common/http.

Open the src/app/app.module.ts file and add the following import statement:

import { HttpClientModule } from "@angular/common/http";

Add then add the HttpClientModule module to the imports array:

imports: [BrowserModule, AppRoutingModule, HttpClientModule],

This makes the HttpClient service available to services and components you define in the project.

We will update the ExpenseService service to retrieve data from the JSON file through HTTP. Open src/app/expenses/expense.service.ts and update the class definition as you see below:

export class ExpenseService {
  private url = "api/expenses.json";
  constructor(private http: HttpClient) {}

  getExpenses(date: string): Observable<IExpense[]> {
    return this.http.get<IExpense[]>(this.url).pipe(
      map(expenses => {
        return expenses.filter(e => e.date.includes(date));
      })
    );
  }

  getTotalSpending(date: string): Observable<number> {
    return this.getExpenses(date).pipe(
      map(expenses => {
        return expenses
          .filter(e => e.date.includes(date))
          .reduce((previous, current) => previous + current.amount, 0);
      })
    );
  }
}

You should notice the HttpClient service is being injected as a dependency and is used in the two methods defined in the class. To make a GET request, you use the get() method in HttpClient with the URL to the service you want to access in the remote server as a parameter. In our case, it’s api/expenses.json, as you can see in the code above. The response can be typed, which is why you see this.http.get<IExpense[]> and that makes the method return the data based on the type specified. The return type for this method is an RxJS Observable.

RxJS (Reactive Extensions for JavaScript) is a library for reactive programming that we can use for composing asynchronous and event-based programs by using observable sequences. Observables help us manage asynchronous data (such as data coming from a Web API), and treats those future values as an invokable collection. Angular’s event system is based on observables.

The pipe() method you saw is used to compose observable operators. Operators are used to manipulate data sequences on observables. They’re methods on observables that transform the source observable. They enable a functional programming style of dealing with collections. In our example, we used the map() operator to filter for expenses that correlate to the date passed in as a parameter.

You would notice some squiggly lines showing that you need to import references for the new type and function you used. Add the import statements below to the file.

import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

Subscribing to Observables

It’s now time to use the service you created in the last section ✌️. Open src/app/home/home.component.ts and update the constructor and lines 18 and 19 with the code below:

constructor(expenseService: ExpenseService) {
  this._expenseService = expenseService;
}

expenses: IExpense[];
currentMonthSpending = {};
lastMonthSpending = {};

What you did was change the default values for the currentMonthSpending and lastMonthSpending properties and removed the call to getExpenses() from the constructor. You may be wondering … why and how would we get the needed data? Tasks such as fetching data from a remote server can take a long time to execute and as such delay the initialization of the component. This is why it’s best practice to keep the constructor light and perform complex initialization tasks outside the constructor. We will put the code to fetch and set the properties in the ngOnInit lifecycle hook. It is in the method that you put code that performs complex initializations shortly after construction. Angular has lifecycle hooks that execute in a particular sequence. I will talk more about the lifecycle hooks in a later post, but you can read about them in the documentation.

The ngOnInit method was added by default when the CLI scaffolded the component class. Add the following code to it.

ngOnInit() {
  const months = [
    "January",
    "February",
    "March",
    "April",
    "May",
    "June",
    "July",
    "August",
    "September",
    "October",
    "November",
    "December"
  ];

  const thisMonth = new Date();
  const lastMonth = new Date(
    thisMonth.getFullYear(),
    thisMonth.getMonth() - 1
  );

  this._expenseService.getExpenses(this.getPeriod(thisMonth)).subscribe(
    expenses => {
      this.expenses = expenses;
    },
    error => {
      console.log("Error retrieving expenses");
      console.error(error);
    }
  );

  this._expenseService
    .getTotalSpending(this.getPeriod(thisMonth))
    .subscribe(amount => {
      this.currentMonthSpending = {
        month: months[thisMonth.getMonth()],
        amount
      };
    });

  this._expenseService
    .getTotalSpending(this.getPeriod(lastMonth))
    .subscribe(amount => {
      this.lastMonthSpending = {
        month: months[lastMonth.getMonth()],
        amount
      };
    });
}

The code you added calls the ExpenseService methods getExpenses() and getTotalSpending() in order to get the current month spending, and a sum of the current and last month’s spending. Those methods return observables and, in order to receive the values, you call .subscribe() on the resulting observable. It is only when you call the subscribe method that the observable will start processing your request and start emitting values.

The .subscribe() method on observables takes up to three arguments, with each of them a handler function:

  1. The first argument is a function that should be executed as the observable emits values while it’s processing the async operation.
  2. The second argument is an error handler function that executes if there is an error.
  3. The third argument is a function that is called when the observable is complete, i.e when the async operation is done.

On lines 43 to 45, we passed a function to set the expenses property of the class to the result of calling this._expenseService.getExpenses(date). You may be wondering why we set this.expenses to the result of observable operation since the data should be coming in one after the other. That’s because HTTP requests are single async operations where a request is sent to the server and the server responds once with all the requested data. Therefore only one value is emitted and hence, the assignment expenses => { this.expenses = expenses; }. It’s also the reason why we used the map operator the way we did in the service class.

In order to make our code complete, we will add one more method to the class. This method is used to transform the date object to the shape we want for the application. Add the method you see below to the class:

  getPeriod(date: Date) {
    const period = date.toJSON().split("-");
    return period[0] + "-" + period[1];
  }

That’s a Wrap

You can start the application by running ng serve -o command and you should see data displayed, and if you open the network panel in your browser, you should see the HTTP request/response to the JSON file.

To recap, an observable can be described as an array whose items arrive asynchronously over time, and you can subscribe to receive the value it emits. You can apply operators to an observable, which can transform the source value into a different shape and return the transformed value to the subscribers. We used the pipe() method to compose observable operators, as you saw in the example. Angular uses observables in its library for dealing with asynchronous operations and the HttpClient service which is used for HTTP operations to return an observable object.

Stay tuned for my next post, which will focus on routing in an Angular application!

The code for this article can be downloaded from GitHub. It’s contained in the src-part-5 folder. If you have any questions, feel free to leave a comment or reach out to me on Twitter.


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.