Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.

Answers by CoreUI Core Team