Performing GET Requests in Angular

This post explains how to perform GET requests in Angular using HttpClient. It includes clear headings and code examples only — no icons or extra visuals. The content covers basic usage, typing responses, query parameters, headers, error handling, RxJS operators, cancellation, caching, testing, performance considerations, security, and best practices.

Table of contents

  1. Introduction
  2. Setting up HttpClientModule
  3. Creating a typed service for GET requests
  4. Calling the service from a component
  5. Handling query parameters and headers
  6. Mapping and transforming responses with RxJS
  7. Error handling strategies
  8. Cancellation and aborting requests
  9. Caching GET responses
  10. Pagination and infinite scroll (GET patterns)
  11. Testing HTTP GET requests
  12. Performance tips and network considerations
  13. Security, CORS, and sensitive data
  14. Advanced patterns: interceptors and retry/backoff
  15. Server-side rendering considerations (Angular Universal)
  16. Real-world examples and patterns
  17. Summary and best practices

1. Introduction

GET requests are used to retrieve data from servers. In Angular, the HttpClient service (from @angular/common/http) provides a modern, typed, and RxJS-friendly API to make HTTP calls. This post focuses on practical patterns for GET requests: typed responses, streaming and mapping data, error handling, cancelation, caching, and testing.


2. Setting up HttpClientModule

Before using HttpClient in your services or components, import HttpClientModule in your root module (or the feature module where you plan to use HTTP).

// app.module.ts

import { NgModule } from ‘@angular/core’;

import { BrowserModule } from ‘@angular/platform-browser’;

import { HttpClientModule } from ‘@angular/common/http’;

import { AppComponent } from ‘./app.component’;

@NgModule({

declarations: [AppComponent],

imports: [BrowserModule, HttpClientModule],

bootstrap: [AppComponent]

})

export class AppModule {}

Once imported, you can inject HttpClient into services or components.


3. Creating a typed service for GET requests

Always type your responses. Define interfaces that match your API responses so TypeScript can help you catch mistakes.

// models/user.ts

export interface Address {

street: string;

suite?: string;

city: string;

zipcode?: string;

}

export interface User {

id: number;

name: string;

username: string;

email: string;

address?: Address;

}

Create a service that performs GET requests. Use generics with HttpClient.get<T>() to strongly type the response.

// services/user.service.ts

import { Injectable } from ‘@angular/core’;

import { HttpClient, HttpParams, HttpHeaders } from ‘@angular/common/http’;

import { Observable } from ‘rxjs’;

import { User } from ‘../models/user’;

@Injectable({ providedIn: ‘root’ })

export class UserService {

private readonly baseUrl = ‘https://jsonplaceholder.typicode.com’;

constructor(private http: HttpClient) {}

getUsers(): Observable<User[]> {

return this.http.get<User[]>(${this.baseUrl}/users);

}

getUserById(id: number): Observable<User> {

return this.http.get<User>(${this.baseUrl}/users/${id});

}

// Example with query params

searchUsers(query: string): Observable<User[]> {

const params = new HttpParams().set(‘q’, query);

return this.http.get<User[]>(${this.baseUrl}/users, { params });

}

}


4. Calling the service from a component

Inject the service into your component and subscribe to the returned observables. Prefer to use the async pipe in templates when possible to avoid manual subscriptions and memory leaks.

// components/user-list.component.ts

import { Component, OnInit } from ‘@angular/core’;

import { Observable } from ‘rxjs’;

import { User } from ‘../models/user’;

import { UserService } from ‘../services/user.service’;

@Component({

selector: ‘app-user-list’,

template: `

<div *ngIf=”users$ | async as users; else loading”>

<pre>{{ users | json }}</pre>

</div>

<ng-template #loading>Loading…</ng-template>

`

})

export class UserListComponent implements OnInit {

users$!: Observable<User[]>;

constructor(private userService: UserService) {}

ngOnInit(): void {

this.users$ = this.userService.getUsers();

}

}

If you must subscribe manually (for side effects), remember to unsubscribe or use takeUntil / firstValueFrom / take(1).

// components/manual-subscribe.component.ts

import { Component, OnDestroy, OnInit } from ‘@angular/core’;

import { Subscription } from ‘rxjs’;

import { UserService } from ‘../services/user.service’;

@Component({ selector: ‘app-manual’, template: ” })

export class ManualSubscribeComponent implements OnInit, OnDestroy {

private sub: Subscription | undefined;

constructor(private userService: UserService) {}

ngOnInit() {

this.sub = this.userService.getUsers().subscribe({

next: users => console.log(‘Users’, users),

error: err => console.error(‘Error’, err)

});

}

ngOnDestroy() {

this.sub?.unsubscribe();

}

}


5. Handling query parameters and headers

HttpParams is immutable; always assign the resulting instance. Use HttpHeaders to add headers.

// services/post.service.ts

import { HttpClient, HttpHeaders, HttpParams } from ‘@angular/common/http’;

import { Injectable } from ‘@angular/core’;

import { Observable } from ‘rxjs’;

@Injectable({ providedIn: ‘root’ })

export class PostService {

constructor(private http: HttpClient) {}

getPosts(page = 1, limit = 10, search?: string): Observable<any> {

let params = new HttpParams().set(‘_page’, String(page)).set(‘_limit’, String(limit));

if (search) {

params = params.set(‘q’, search);

}

const headers = new HttpHeaders({ ‘X-App-Version’: ‘1.0.0’ });

return this.http.get(‘/api/posts’, { params, headers });

}

}

Use URLSearchParams for server-side or non-Angular use, but prefer HttpParams inside Angular services.


6. Mapping and transforming responses with RxJS

Use RxJS operators to transform or enrich data before it reaches components.

// services/user.service.ts (extended)

import { map } from ‘rxjs/operators’;

getUserNames(): Observable<string[]> {

return this.http.get<User[]>(${this.baseUrl}/users).pipe(

map(users => users.map(u => u.name))

);

}

Combine requests with forkJoin, combineLatest, zip, or switchMap as required.

// example combining two independent GETs

import { forkJoin } from ‘rxjs’;

const users$ = this.http.get<User[]>(${baseUrl}/users);

const posts$ = this.http.get<any[]>(${baseUrl}/posts);

forkJoin({ users: users$, posts: posts$ }).subscribe(result => {

console.log(result.users, result.posts);

});

When reacting to route parameter changes, use switchMap to cancel previous requests.

// route-driven example

this.route.paramMap.pipe(

map(params => Number(params.get(‘id’))),

switchMap(id => this.userService.getUserById(id))

).subscribe(user => this.user = user);


7. Error handling strategies

Handle errors with catchError and return a safe fallback or rethrow as needed. Log errors centrally with an interceptor for shared behavior.

import { catchError } from ‘rxjs/operators’;

import { throwError, of } from ‘rxjs’;

getUsersSafe(): Observable<User[]> {

return this.http.get<User[]>(${this.baseUrl}/users).pipe(

catchError(error => {

// handle error, log it, and return a fallback value

console.error(‘GET users failed’, error);

return of([] as User[]); // fallback

})

);

}

If the UI needs to show error messages, pass the error to the component via the observable and let the UI decide how to display it.

this.userService.getUsers().subscribe({

next: data => (this.users = data),

error: err => (this.loadError = err)

});


8. Cancellation and aborting requests

You can cancel HTTP requests using the takeUntil pattern or AbortController (Angular 14+ supports passing an AbortSignal). Use switchMap to cancel prior requests when a new one arrives.

// takeUntil example

import { Subject } from ‘rxjs’;

import { takeUntil } from ‘rxjs/operators’;

private destroy$ = new Subject<void>();

ngOnInit() {

this.userService.getUsers()

.pipe(takeUntil(this.destroy$))

.subscribe(users => (this.users = users));

}

ngOnDestroy() {

this.destroy$.next();

this.destroy$.complete();

}

// AbortController example (Angular supports ‘signal’ option)

const controller = new AbortController();

this.http.get(‘/api/long’, { signal: controller.signal }).subscribe();

// later

controller.abort();

Using switchMap when handling rapidly changing inputs (search boxes, route params) ensures previous HTTP calls are cancelled.


9. Caching GET responses

Implement caching where appropriate to reduce network calls. Simple in-memory cache:

// services/cache.service.ts

import { Injectable } from ‘@angular/core’;

import { Observable, of } from ‘rxjs’;

import { tap } from ‘rxjs/operators’;

@Injectable({ providedIn: ‘root’ })

export class CacheService {

private cache = new Map<string, any>();

get<T>(key: string): T | undefined {

return this.cache.get(key);

}

set<T>(key: string, value: T): void {

this.cache.set(key, value);

}

getOrFetch<T>(key: string, fetch$: Observable<T>): Observable<T> {

const cached = this.get<T>(key);

if (cached) {

return of(cached);

}

return fetch$.pipe(tap(value => this.set(key, value)));

}

}

Use it in your service:

// services/user.service.ts (with cache)

getUsersCached() {

const key = ‘users_all’;

return this.cacheService.getOrFetch(key, this.http.get<User[]>(${this.baseUrl}/users));

}

For larger apps, use an interceptor that caches GET requests based on URL and parameters and respects cache headers.


10. Pagination and infinite scroll (GET patterns)

Server-side pagination is preferred. Use query parameters for page, limit, offset, or cursor-based pagination. Example of cursor-based pattern:

getItems(cursor?: string, limit = 20) {

let params = new HttpParams().set(‘limit’, String(limit));

if (cursor) params = params.set(‘cursor’, cursor);

return this.http.get<{ items: any[]; nextCursor?: string }>(/api/items, { params });

}

Implement infinite scroll by requesting the next page when the user scrolls near the bottom, appending items to the list.


Comments

Leave a Reply

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