← Back to Articles         
Angular HTTP Configuration: Before vs Standalone APIs

Angular HTTP Configuration: Before vs Standalone APIs

Published on 02 Oct 2026     9 min read Angular
Older Angular vs Modern Angular

Angular HTTP Configuration: Before vs Standalone APIs

These three questions are closely related because Angular moved from NgModule-based HTTP configuration to provider-based HTTP configuration.

The basic evolution is:

BEFORE

HttpClientModule
      +
HTTP_INTERCEPTORS
      ↓
NgModule configuration


NOW

provideHttpClient()
      +
withInterceptors(...)
      ↓
Standalone provider configuration

1. How Was HttpClientModule Configured Before Standalone APIs?

What is HttpClientModule?

Before standalone APIs, Angular's HttpClient was normally made available by importing:

HttpClientModule

from:

@angular/common/http

into an Angular NgModule, usually AppModule.

Without importing HttpClientModule, Angular's dependency-injection system would not have the providers needed for HttpClient.


Traditional Architecture

main.ts
   ↓
AppModule
   ↓
imports: [HttpClientModule]
   ↓
HttpClient available
   ↓
ProductService
   ↓
ASP.NET Core Web API

Full Simple Program — Old NgModule Approach

Assume Angular calls:

GET https://localhost:7001/api/products

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
  ],

  providers: [],

  bootstrap: [
    AppComponent
  ]

})
export class AppModule {
}

The important configuration was:

imports: [
  HttpClientModule
]

Once imported, HttpClient could be injected into services.


product.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private apiUrl =
    'https://localhost:7001/api/products';

  constructor(private http: HttpClient) {
  }

  getProducts(): Observable<Product[]> {

    return this.http.get<Product[]>(
      this.apiUrl
    );

  }
}

Because HttpClientModule had been imported, Angular could resolve:

HttpClient

when constructing ProductService.


Old HTTP Flow

AppModule
   ↓
HttpClientModule
   ↓
registers HTTP providers
   ↓
HttpClient
   ↓
ProductService
   ↓
http.get()
   ↓
ASP.NET Core Web API

2. How Is HTTP Configured Using provideHttpClient()?

With standalone Angular, we generally don't need to import HttpClientModule into a root AppModule.

Instead, HTTP is configured using:

provideHttpClient()

Modern Standalone Example

app.config.ts

import {
  ApplicationConfig
} from '@angular/core';

import {
  provideHttpClient
} from '@angular/common/http';

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient()

  ]

};

That's enough to make HttpClient available application-wide.


main.ts

import {
  bootstrapApplication
} from '@angular/platform-browser';

import { App } from './app/app';
import { appConfig } from './app/app.config';

bootstrapApplication(App, appConfig)
  .catch(err => console.error(err));

The flow is:

main.ts
   ↓
bootstrapApplication(App, appConfig)
                         ↓
                   app.config.ts
                         ↓
               provideHttpClient()
                         ↓
                    HttpClient
                         ↓
                  HTTP requests

product.service.ts

The service itself is almost unchanged:

import { Injectable } from '@angular/core';

import {
  HttpClient
} from '@angular/common/http';

import {
  Observable
} from 'rxjs';

export interface Product {
  id: number;
  name: string;
  price: number;
}

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private apiUrl =
    'https://localhost:7001/api/products';

  constructor(private http: HttpClient) {
  }

  getProducts(): Observable<Product[]> {

    return this.http.get<Product[]>(
      this.apiUrl
    );

  }
}

Notice something important:

Old service

constructor(private http: HttpClient) {}

Modern service

constructor(private http: HttpClient) {}

The way we use HttpClient did not fundamentally change.

What changed is primarily how its providers are configured.


Old vs Modern HTTP Configuration

Before

@NgModule({

  imports: [
    HttpClientModule
  ]

})
export class AppModule {
}

Modern

export const appConfig: ApplicationConfig = {

  providers: [
    provideHttpClient()
  ]

};

Easy way to remember:

OLD
HttpClientModule
     ↓
imports


MODERN
provideHttpClient()
     ↓
providers

3. How Were Interceptors Registered Using HTTP_INTERCEPTORS?

Before functional interceptors became the preferred standalone style, interceptors were commonly implemented as classes implementing:

HttpInterceptor

They were then registered with the DI token:

HTTP_INTERCEPTORS

What Is an HTTP Interceptor?

An interceptor sits between your Angular application and HTTP communication.

For example:

Component
   ↓
ProductService
   ↓
HttpClient
   ↓
Interceptor
   ↓
ASP.NET Core API

It can perform cross-cutting operations such as:

  • Add JWT tokens
  • Add correlation IDs
  • Log requests
  • Handle errors
  • Show loading indicators

For example:

Angular
   ↓
AuthInterceptor
   ↓
Authorization: Bearer <token>
   ↓
ASP.NET Core Web API

Full Old-Style Interceptor Example

Suppose we want to add a JWT token to every API request.

auth.interceptor.ts

import { Injectable } from '@angular/core';

import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from '@angular/common/http';

import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    const token = 'sample-jwt-token';

    const clonedRequest = req.clone({

      setHeaders: {

        Authorization:
          `Bearer ${token}`

      }

    });

    return next.handle(clonedRequest);
  }
}

Registering the Interceptor

In AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import {
  HttpClientModule,
  HTTP_INTERCEPTORS
} from '@angular/common/http';

import { AppComponent } from './app.component';

import {
  AuthInterceptor
} from './auth.interceptor';

@NgModule({

  declarations: [
    AppComponent
  ],

  imports: [
    BrowserModule,
    HttpClientModule
  ],

  providers: [

    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }

  ],

  bootstrap: [
    AppComponent
  ]

})
export class AppModule {
}

The important code is:

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Understanding This Registration

provide

provide: HTTP_INTERCEPTORS

This tells Angular:

Register something against Angular's HTTP interceptor injection token.


useClass

useClass: AuthInterceptor

This tells Angular:

Use AuthInterceptor as an interceptor implementation.


multi: true

This is particularly important.

multi: true

means:

Allow multiple providers to be registered for HTTP_INTERCEPTORS.

Without multi-provider behavior, multiple interceptor registrations would not compose as the intended interceptor collection.

For example:

HTTP_INTERCEPTORS
      │
      ├── AuthInterceptor
      ├── LoggingInterceptor
      └── ErrorInterceptor

Multiple Old-Style Interceptors

For example:

providers: [

  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  },

  {
    provide: HTTP_INTERCEPTORS,
    useClass: LoggingInterceptor,
    multi: true
  },

  {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor,
    multi: true
  }

]

Conceptually:

HTTP Request
     ↓
AuthInterceptor
     ↓
LoggingInterceptor
     ↓
ErrorInterceptor
     ↓
ASP.NET Core Web API

The response travels back through the interceptor chain.


What Is the Modern Approach?

Modern standalone Angular supports functional interceptors, configured with:

provideHttpClient(
  withInterceptors(...)
)

For example:

provideHttpClient(
  withInterceptors([
    authInterceptor,
    loggingInterceptor,
    errorInterceptor
  ])
)

Modern Functional Interceptor

auth.interceptor.ts

import {
  HttpInterceptorFn
} from '@angular/common/http';

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    const token = 'sample-jwt-token';

    const clonedRequest = req.clone({

      setHeaders: {

        Authorization:
          `Bearer ${token}`

      }

    });

    return next(clonedRequest);
  };

Notice:

HttpInterceptorFn

instead of:

class AuthInterceptor
  implements HttpInterceptor

app.config.ts

import {
  ApplicationConfig
} from '@angular/core';

import {
  provideHttpClient,
  withInterceptors
} from '@angular/common/http';

import {
  authInterceptor
} from './auth.interceptor';

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient(

      withInterceptors([
        authInterceptor
      ])

    )

  ]

};

The important code is:

provideHttpClient(
  withInterceptors([
    authInterceptor
  ])
)

Complete Modern Example With Multiple Interceptors

Suppose we have:

authInterceptor
loggingInterceptor
errorInterceptor

Configuration:

provideHttpClient(

  withInterceptors([

    authInterceptor,

    loggingInterceptor,

    errorInterceptor

  ])

)

Architecture:

Angular Component
       ↓
ProductService
       ↓
HttpClient
       ↓
authInterceptor
       ↓
loggingInterceptor
       ↓
errorInterceptor
       ↓
ASP.NET Core Web API

Old vs Modern Interceptors

Old Class-Based Approach

Interceptor

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ) {

    return next.handle(req);

  }

}

Registration

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Modern Functional Approach

Interceptor

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    return next(req);

  };

Registration

provideHttpClient(

  withInterceptors([
    authInterceptor
  ])

)

This is substantially simpler.


Important: Can Class-Based Interceptors Still Be Used?

Yes. This is useful when maintaining or migrating an older application.

With provider-based HTTP configuration, DI-provided/class-based interceptors can be included using:

withInterceptorsFromDi()

For example:

import {
  HTTP_INTERCEPTORS,
  provideHttpClient,
  withInterceptorsFromDi
} from '@angular/common/http';

export const appConfig: ApplicationConfig = {

  providers: [

    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },

    provideHttpClient(
      withInterceptorsFromDi()
    )

  ]

};

So:

Class interceptor
      ↓
HTTP_INTERCEPTORS
      ↓
withInterceptorsFromDi()
      ↓
provideHttpClient()

However, for new standalone code, functional interceptors are generally the cleaner approach.


Complete Evolution

Traditional Angular

AppModule
   │
   ├── imports
   │      ↓
   │  HttpClientModule
   │
   └── providers
          ↓
      HTTP_INTERCEPTORS
          ↓
      AuthInterceptor

Code:

@NgModule({

  imports: [
    HttpClientModule
  ],

  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ]

})
export class AppModule {
}

Modern Standalone Angular

app.config.ts
     ↓
provideHttpClient()
     ↓
withInterceptors()
     ↓
Functional interceptors

Code:

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient(

      withInterceptors([
        authInterceptor,
        errorInterceptor
      ])

    )

  ]

};

Side-by-Side Comparison

Before standalone APIs Modern standalone approach
HttpClientModule provideHttpClient()
NgModule imports Application providers
Class interceptor Functional interceptor preferred
HttpInterceptor HttpInterceptorFn
HTTP_INTERCEPTORS withInterceptors()
useClass Function reference
multi: true Interceptor array
next.handle(req) next(req)

Key Points

Old HTTP configuration

imports: [
  HttpClientModule
]

was used to configure Angular HTTP support.

Modern HTTP configuration

providers: [
  provideHttpClient()
]

is the provider-based approach.

Old interceptor

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {
}

registered using:

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Modern interceptor

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {
    return next(req);
  };

registered using:

provideHttpClient(
  withInterceptors([
    authInterceptor
  ])
)

Easy way to remember

OLD

HttpClientModule
HTTP_INTERCEPTORS
HttpInterceptor
next.handle(req)


NOW

provideHttpClient()
withInterceptors()
HttpInterceptorFn
next(req)

Interview Questions and Answers

1. How was HttpClient configured before standalone APIs?

HttpClientModule was imported into an NgModule, usually AppModule.

imports: [
  HttpClientModule
]

This registered the providers required for HttpClient.


2. How is HttpClient configured in standalone Angular?

Use:

provideHttpClient()

typically in app.config.ts:

providers: [
  provideHttpClient()
]

3. Does using HttpClient inside a service change?

Not significantly.

You can still inject it:

constructor(private http: HttpClient) {}

or use:

private http = inject(HttpClient);

The main change is how HTTP support is provided/configured.


4. How were interceptors traditionally created?

They were classes implementing:

HttpInterceptor

Example:

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {
}

5. How were class-based interceptors registered?

Using:

HTTP_INTERCEPTORS

as a multi-provider:

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

6. Why was multi: true required?

Because Angular needed to allow multiple interceptors to be registered under the same HTTP_INTERCEPTORS token.

HTTP_INTERCEPTORS
    ├── AuthInterceptor
    ├── LoggingInterceptor
    └── ErrorInterceptor

7. What is the modern interceptor approach?

Modern Angular commonly uses functional interceptors:

export const authInterceptor: HttpInterceptorFn =
  (req, next) => next(req);

registered with:

provideHttpClient(
  withInterceptors([
    authInterceptor
  ])
)

8. What is withInterceptors()?

It configures functional HTTP interceptors for the HttpClient instance provided by provideHttpClient().

provideHttpClient(
  withInterceptors([
    authInterceptor,
    errorInterceptor
  ])
)

9. Can multiple functional interceptors be registered?

Yes.

withInterceptors([
  authInterceptor,
  loggingInterceptor,
  errorInterceptor
])

The order matters because they form an interceptor chain.


10. Can old class-based interceptors still work with provideHttpClient()?

Yes. DI-provided interceptors can be included using:

provideHttpClient(
  withInterceptorsFromDi()
)

along with their HTTP_INTERCEPTORS provider registrations.


Interview Quick Answer

Before standalone APIs, Angular HTTP support was configured by importing HttpClientModule into an NgModule, and class-based interceptors were registered using the HTTP_INTERCEPTORS multi-provider. In modern standalone Angular, HTTP is configured with provideHttpClient(), and functional interceptors are commonly registered using withInterceptors().

← Back to Articles