How to integrate Angular with REST API
Integrating Angular with a REST API is a fundamental task in every enterprise application, and doing it correctly requires understanding Angular’s HttpClient module and RxJS observables.
As the creator of CoreUI and an Angular developer since 2014, I’ve built REST integrations for dozens of production dashboards and admin panels.
The cleanest approach is to encapsulate all HTTP calls in a dedicated service, inject it into components, and handle responses reactively using RxJS operators.
This keeps components focused on presentation and makes API logic easy to test.
Import HttpClientModule and create a service to encapsulate API calls.
// app.config.ts (Angular 17+ standalone)
import { ApplicationConfig } from '@angular/core'
import { provideHttpClient } from '@angular/common/http'
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient()
]
}
// user.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
export interface User {
id: number
name: string
email: string
}
@Injectable({ providedIn: 'root' })
export class UserService {
private readonly apiUrl = 'https://api.example.com'
constructor(private http: HttpClient) {}
getUsers(): Observable<User[]> {
return this.http.get<User[]>(`${this.apiUrl}/users`)
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/users/${id}`)
}
createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<User>(`${this.apiUrl}/users`, user)
}
updateUser(id: number, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/users/${id}`, user)
}
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/users/${id}`)
}
}
HttpClient.get<T>() returns a typed Observable<T>. The generic type parameter tells TypeScript what shape to expect from the response. The service is providedIn: 'root' so it’s a singleton available throughout the app.
Consuming the Service in a Component
Subscribe to the observable in the component to receive data.
// users.component.ts
import { Component, OnInit } from '@angular/core'
import { CommonModule } from '@angular/common'
import { UserService, User } from './user.service'
@Component({
selector: 'app-users',
standalone: true,
imports: [CommonModule],
template: `
<div *ngIf="loading">Loading...</div>
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
<ul *ngIf="users.length">
<li *ngFor="let user of users">{{ user.name }} - {{ user.email }}</li>
</ul>
`
})
export class UsersComponent implements OnInit {
users: User[] = []
loading = false
error = ''
constructor(private userService: UserService) {}
ngOnInit(): void {
this.loading = true
this.userService.getUsers().subscribe({
next: (users) => {
this.users = users
this.loading = false
},
error: (err) => {
this.error = err.message
this.loading = false
}
})
}
}
The subscribe method accepts an observer object with next, error, and complete callbacks. Always handle the error callback — unhandled HTTP errors will crash the observable stream.
Handling Errors Globally with an Interceptor
Intercept all HTTP errors in one place instead of handling them in each service.
// error.interceptor.ts
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'
import { catchError, throwError } from 'rxjs'
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
// redirect to login
}
const message = error.error?.message ?? error.message
return throwError(() => new Error(message))
})
)
}
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http'
import { errorInterceptor } from './error.interceptor'
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([errorInterceptor]))
]
}
Interceptors transform every request and response. Using catchError in an interceptor means you handle 401, 403, and 500 errors consistently without duplicating logic in every service.
Adding Auth Headers with an Interceptor
Attach authorization tokens to outgoing requests automatically.
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http'
import { inject } from '@angular/core'
import { AuthService } from './auth.service'
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const auth = inject(AuthService)
const token = auth.getToken()
if (!token) return next(req)
const authReq = req.clone({
setHeaders: { Authorization: `Bearer ${token}` }
})
return next(authReq)
}
req.clone() creates a new immutable request with the headers attached. HTTP requests in Angular are immutable, so you must clone before modifying. Returning next(authReq) passes the modified request down the chain.
Best Practice Note
This is the same service-layer pattern we use in CoreUI Angular templates — all HTTP calls live in services, components only subscribe. For production apps, consider using the async pipe in templates instead of manual subscribe() calls to avoid memory leaks from unsubscribed observables. You can also use Angular with GraphQL for more flexible data querying when your API supports it.



