Making HTTP Requests to API in Angular – HttpClient and Interceptors

Single Page Applications often rely on HTTP requests for requesting data from APIs through which they operate on the data fetched into powerful presentation logic or front-end transformations. Angular facilitates making HTTP calls to APIs using its HttpClient library

Single Page Applications often rely on HTTP requests for requesting data from APIs through which they operate on the data fetched into powerful presentation logic or front-end transformations.

For example, a Weather application built on a front-end technology such as Angular itself doesn’t access the backend databases or domain systems for data it shows. Instead, it makes requests to a server-side WeatherForecast API which encapsulates the business or data logic using which it reads from the underlying database or domain systems and returns appropriate Weather data to the application, which then applies its own features and presents to the user.

"Angular facilitates making HTTP calls to APIs using its HttpClient library which is a part of the @angular/common/http package."

Consider the SocialApp which displays a list of Posts created by various Users and enables one to interact with it. The core feature of this application is showing the post list, which obviously requires the application to fetch data from a remote API.

Working with HttpClient:

To do this, we use the HttpClient package for working with API requests. To begin with, we add the HttpClientModule to the list of imports in our Module class.

@NgModule({
    declarations: [
        PostItemComponent,
        PostListComponent,
        NewPostComponent
    ],
    imports: [
        -- other imports --
        HttpClientModule
    ],
})
export class PostsModule { }

Once included, we can now access the HttpClient service in all the Components and Services local to that Module.

Next, create a service class which shall provide us with Posts related functionalities for the requesting components. This PostsService class is responsible for making HTTP request to the APIs and transforming the response data according to the components.

@Injectable({
    providedIn: "root"
})
export class PostsService {
    private posts: Post[] = [];
    private apiUri = "http://server-api-domain.com/api";

    constructor(private http: HttpClient) { }

    --- other methods ---

    // GET all posts
    // from the API
    getPosts(): Observable<Post[]> {
        return this.http.get<Post[]>(`${this.apiUri}/posts`)
            .pipe(map((res: Post[]) => {
                this.posts = [...res];
                --- other logic ---
                return res;
        }));
    }

    // GET single post
    // from the API
    getPost(id: string): Observable<Post> {
        return this.http.get<Post>(`${this.apiUri}/posts/${id}`);
    }

    // POST new post item
    // to the API
    createPost(status: string): Observable<Post> {
        let post: Post = {
            Text: status,
            Type: PostType.Status,
            Id: null,
            AssetUrl: null,
            PostedBy: Guid.create().toString(),
            PostedOn: new Date()
        };

        return this.http.post<Post>(`${this.apiUri}/posts`, post)
            .pipe(map((res) => {
                this.posts.push(res);
                --- other logic --
                return res;
        }));
    }
}

Observe that the PostsService offers three methods which fetches ALL the posts from the database, a SINGLE post from the database and POSTs a new post item to the database.

Internal to each method is an individual API call using the HttpClient object, which is injected via the constructor.

the HttpClient class provides methods to perform the four primary API operations:

  • http.get(api_url) – for GET requests to read data
  • http.post(api_url, data_object) – for POST requests to insert data
  • http.put(api_url, data_object) – for PUT requests to replace an object in the database
  • http.patch(api_url, data_object) – for PATCH requests to update one or more properties of an object in the database

Each of these methods return an Observable which contains the response content from the API.

#similarly for other methods#
// var res = this.http.post<Post>(`${this.apiUri}/posts`, post);
// var res = this.http.put<Post>(`${this.apiUri}/posts`, post);
// var res = this.http.patch<Post>(`${this.apiUri}/posts`, post)

var res = this.http.get<Post[]>(`${this.apiUri}/posts`);
res.subscribe((posts) => {
    // posts is an array of Post objects
})

In the above code, take for example the getPosts() method which receives all post objects from the database. To do this, we call http.get() method with respective API endpoint. We also pass the type which we’re expecting from this API call, in our case it is an array of Post objects.

However, since we would want to pass this subscription to the calling component which can subscribe to this and receives data, we return the subscription in our getPosts() method. However, we want to maintain a local copy of the Posts array at the service level so that we can trigger events to subscribed components for any changes in this Posts array. For this, we want to get hold of the response from the http.get() method, and pass on the subscription once we’re done with our job. How do we do this? Enter pipe(map()).

Pipe and Map:

Observable provides pipe() method using which we can temporarily attach to the Observable stream and pass to the next listener after performing some operations. Inside pipe() we use the map() which is an RxJs operator. map operator is similar to JavaScript map() method which can help us to map an incoming object into another object.

Together, we grab the HttpResponse stream which contains a Post[] content and then push the incoming response Post array into our local post array. Finally we return the response content so that it goes on to the next listener on the Observable stream.

.pipe(map((res: Post[]) => {
    this.posts = [...res];
    --- other logic ---
    return res;
}

On the component side, we subscribe to the getPosts() method on the PostsService service and once the response content is available, we assign the returned post array to our model which is dynamically bound to the view.

@Component({
  selector: 'app-post-list',
  templateUrl: './post-list.component.html',
  styleUrls: ['./post-list.component.css']
})
export class PostListComponent implements OnInit, OnDestroy {

  posts: Post[] = [];

  constructor(private postsService: PostsService, router: Router) { }

  ngOnDestroy(): void {
  }

  ngOnInit(): void {
    this.postsService.getPosts().subscribe((posts) => {
      this.posts = [...posts];
    });
  }
}
<a *ngFor="let post of posts;" [routerLink]="['/posts', post?.Id]">
    <div class="card my-2">
        <div class="card-body">
            <div class="container-fluid">
                <p class="card-text">{{post?.Text}}</p>
            </div>
        </div>
    </div>
</a>

"Keep in mind that a call for HTTP request using HttpClient starts only when subscribe() method is called on the Observable the http method returns."

Each time when we call subscribe() method on the Observable stream, a new individual request is created from the client.

var obs = this.postsService.getPosts();

// original request 1
obs.subscribe((posts) => {
    // original response 1
});

// original request 2
obs.subscribe((posts) => {
    // original response 2
});

// original request 3
obs.subscribe((posts) => {
    // original response 3
});

HTTP Interceptors:

Sometimes we would need to add a few more dynamic parameters to such HTTP API calls before they’re passed onto the remote API services. Or read something from the response before it is forwarded to the calling services.

Some examples would be

  • to add an Authentication header which authorizes the client request at the API end
  • adding a timestamp query parameter at the end of all requests so that the API recognizes each request uniquely
  • capture an ETag from the response headers as the response arrives from the API and preserve it
  • read the response headers for any other required values

and so on.

In such cases, its not a good practice to replicate the same logic in all the methods of the service. Instead, we can encapsulate this functionality inside a utility and attach this so as to monitor all outgoing requests or incoming responses and do something. These components are called as Interceptors.

Let’s say we want to pass in a custom Header called X-Request-ID in all our Post API calls before pushing it to the network. To do this, we create our own custom Interceptor class which implements HttpInterceptor and encapsulate this functionality:

import { 
    HttpInterceptor, 
    HttpRequest, 
    HttpHandler, 
    HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Guid } from 'guid-typescript';

export class PostsInterceptor implements HttpInterceptor {

    intercept(req: HttpRequest<any>, next: HttpHandler): 
        Observable<HttpEvent<any>> {
        
        // functionality specific to
        // this Interceptor - adding a request header
        if (requestPath.indexOf('posts') > -1) {
            var xRequestId = Guid.create().toString();
            var headers = req.headers.append("X-Request-ID", xRequestId);
            req = req.clone({
                headers: headers
            });
        }

        // passes on the request to next handler
        // once all the handlers are executed
        // the request is now pushed into the network
        // for handling by the API
        return next.handle(req);
    }
}

Observe that in this, we add our intermediate logic to be performed on the outgoing request – in this case, adding a request header and then we pass on the request to be handled by the next handler. In an Angular application, a request passes over several such in-built handlers before finally passed onto the API.

Finally we add this interceptor implementation inside our Module class under Providers.

@NgModule({
    declarations: [
        PostItemComponent,
        PostListComponent,
        NewPostComponent
    ],
    imports: [
        CommonModule,
        RouterModule.forChild(routes),
        FormsModule,
        HttpClientModule
    ],
    providers: [
        {
            provide: HTTP_INTERCEPTORS,
            useClass: PostsInterceptor,
            multi: true
        }
    ]
})
export class PostsModule { }

When we run this and check our outgoing requests, we can see the xRequestId header added to our final Request.

wp-content/uploads/2022/05/interceptor-http.png

Apart from handling requests or responses, Interceptors can also be used to handle Errors globally before being caught at the calling components.

In this way, we can make API requests from our Angular application using the HttpClient library and add listeners for preprocessing the requests or postprocessing the responses using the HttpInterceptors.

Sriram Mannava
Sriram Mannava

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity.

Leave a Reply

Your email address will not be published. Required fields are marked *