Author avatar

Zachary Bennett

How to Make Type Safe HTTP Requests in Angular

Zachary Bennett

  • Jun 4, 2020
  • 6 Min read
  • 506 Views
  • Jun 4, 2020
  • 6 Min read
  • 506 Views
Languages Frameworks and Tools
Front End Web Developer
Client-side Frameworks
Angular

Introduction

One of the most powerful features that Angular provides is type safety through the use of TypeScript. Statically typed languages like TypeScript allow you to write more maintainable and easy-to-read code that is far less vulnerable to runtime errors. This guide will show you how you can use custom types together with Angular's HttpClient to create powerfully simple and declarative HTTP requests that are more bug-free and easier to maintain as your app scales.

Getting Started Using the Angular HttpClient

First, this guide will go over a simple example of how you would use Angular's HttpClient without the expressivity of types. Here is an example service that provides a method for making a simple HTTP GET request for dog breeds from the server.

1
2
3
4
5
6
7
8
9
10
11
12
13
    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';

    @Injectable()
    export class DogBreedsService {
        private breedsApiUrl = '/api/breeds';

        constructor(private http: HttpClient) { }

        getBreeds(): Observable<any> {
            return this.http.get(this.breedsApiUrl);
        }
    }
typescript

Below is a simple Angular component that uses the service above.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';

@Component({
  selector: 'app-dog-breeds',
  template: `
    <ul *ngFor="let breed of breeds$">
        <li>{{ breed.displayName }}</li>
    </ul>
    `
})
export class DogBreedsComponent {
    breeds$: Observable<any>;

    constructor(private breedsService: DogBreedsService) {
        this.breeds$ = this.breedsService.getBreeds();
    }
}
typescript

Most of the above code is fine. However there are a couple of problems with it, most of which are directly related to the lack of type safety.

Because of the way TypeScript's built-in any type is used, you would have negated any form of type safety and thus excluded the benefits of developer tooling like Intellisense--all whilst making the code harder to maintain and more prone to runtime errors. In fact, because the HTTP request has not been composed with types, you would have introduced a bug of which you'd be completely unaware. In the next section, you will identify this bug and add a type to your HTTP request in order to guard against errors similar to this one in the future.

Creating the Type

First, create your Breed type model. You can create your type via a TypeScript interface, which is essentially a contract that your code agrees to adhere to when using this type. Your simple Breed type will look like the following:

1
2
3
4
5
interface Breed {
    id: string;
    name: string;
    relatedBreeds?: Breed[];
}
typescript

Your Breed type consists of three fields: id, name, and relatedBreeds. With your data model for dog breeds created, you can now start to use your type to make the code more expressive, more readable, and less error-prone. After all, Breed is much more expressive of what the DogBreedsService is all about when compared to any!

Note: In order to use this type within other TypeScript files, you will need to export it using the export keyword.

Integrating the Type with the HttpClient

With the following code, you'll make sure that your HTTP request uses the previously created Breed type.

1
2
3
4
5
6
7
8
9
10
11
12
13
    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';

    @Injectable()
    export class DogBreedsService {
        private breedsApiUrl = '/api/breeds';

        constructor(private http: HttpClient) { }

        getBreeds(): Observable<Breed[]> {
            return this.http.get<Breed[]>(this.breedsApiUrl);
        }
    }
typescript

Two small, but very powerful, changes were made to the DogBreedsService. Both changes, implemented above, consisted of the addition of what are called type parameters. Type parameters are a fundamental component of any robust type system and are used to create generic types. Under the hood, the Angular HttpClient module's function get is a generic function that accepts a type parameter. This allows you to express through your code that what will be returned by the execution of this HTTP request is indeed an array of type Breed. In the code above, just before the get function is called, you can see the <Breed> syntax--this is an example of a generic type parameter.

Now, you need to update your component that uses the altered service!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';

@Component({
  selector: 'app-dog-breeds',
  template: `
    <ul *ngFor="let breed of breeds$">
        <li>{{ breed.name }}</li>
    </ul>
    `
})
export class DogBreedsComponent {
    breeds$: Observable<Breed[]>;

    constructor(private breedsService: DogBreedsService) {
        this.breeds$ = this.breedsService.getBreeds();
    }
}
typescript

According to the Angular documentation, attaching a type to the request actually allows for type assertion at compile time. This is very powerful! After compiling and running your altered code, you can see your error: the field displayName does not exist on the type Breed. By way of adding types to your HTTP request, you now have a structure and contract that you can follow in order to iterate on your code faster by way of Intellisense and other developer tooling built around the Angular and TypeScript ecosystem.

Conclusion

The Angular HttpClient is a powerful and robust HTTP client that allows you to write expressive and type-safe code by way of the generic functions that it exposes. By implementing your own custom types and passing them to the HttpClient as type parameters, you can increase your productivity while decreasing the amount of runtime errors within your app. What's not to love about that?

9