Services and dependency injection

Injectable services, the inject() function, providers and tokens, plus an HTTP layer with interceptors and typed responses.

Writing and consuming a service

// cart.store.ts
import { Injectable, signal, computed } from '@angular/core'

@Injectable({ providedIn: 'root' })   // one instance for the whole application
export class CartStore {
  private readonly items = signal<Item[]>([])

  readonly count = computed(() => this.items().length)
  readonly total = computed(() => this.items().reduce((t, i) => t + i.price, 0))

  add(item: Item) {
    this.items.update(list => [...list, item])
  }

  remove(id: string) {
    this.items.update(list => list.filter(i => i.id !== id))
  }
}
import { Component, inject } from '@angular/core'
import { CartStore } from './cart.store'

@Component({ selector: 'app-cart', template: '{{ cart.total() }}' })
export class CartComponent {
  readonly cart = inject(CartStore)     // resolved from the injector, no constructor ceremony
}
  • providedIn: 'root' registers a tree-shakable singleton that is created on first injection — the default for application-wide services.
  • inject() works in constructors and field initialisers, and is the only way to get a dependency inside a functional guard, interceptor or resolver.
  • providedIn: 'any' and component-level providers give each lazy module or component instance its own copy.
  • Keep a service free of template concerns: it should expose state and operations, not formatted strings for one screen.

Providers, tokens and overrides

// app.config.ts
import { ApplicationConfig, inject } from '@angular/core'
import { provideRouter } from '@angular/router'
import { provideHttpClient, withInterceptors } from '@angular/common/http'
import { routes } from './app.routes'

export const API_BASE = new InjectionToken<string>('API_BASE')

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor])),
    { provide: API_BASE, useValue: 'https://api.example.com' }
  ]
}
// a component-level override, useful in tests and previews
@Component({
  selector: 'app-preview',
  providers: [{ provide: API_BASE, useValue: 'https://staging.example.com' }],
  template: '...'
})
export class PreviewComponent {}

// in a unit test
TestBed.configureTestingModule({
  providers: [{ provide: CartStore, useValue: fakeCart }]
})
Provider formUse when
useClassSwapping one implementation for another
useValueConfig constants, fakes and mocks
useFactoryThe value must be computed or depends on other services
useExistingTwo tokens should resolve to the same instance
InjectionTokenA non-class dependency, especially configuration
⚠️
A component that declares its own providers gets a private instance for every instance of that component — a common reason two parts of a screen disagree about state that a shared service should own.

HTTP with interceptors

import { Injectable, inject } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { API_BASE } from '../app.config'

export interface Guide { id: string; title: string; tags: string[] }

@Injectable({ providedIn: 'root' })
export class GuideService {
  private http = inject(HttpClient)
  private base = inject(API_BASE)

  list() {
    return this.http.get<Guide[]>(this.base + '/guides')
  }

  create(payload: { title: string }) {
    return this.http.post<Guide>(this.base + '/guides', payload)
  }
}
// auth.interceptor.ts — functional interceptor, registered in app.config.ts
import { HttpInterceptorFn } from '@angular/common/http'

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('token')
  if (!token || req.url.startsWith('https://cdn.')) return next(req)

  return next(req.clone({ setHeaders: { Authorization: 'Bearer ' + token } }))
}
  • HttpClient returns cold observables: nothing happens until something subscribes, which is why templates use the async pipe or a resource wrapper.
  • Interceptors are the right place for auth headers, retries, correlation ids and global error reporting — one implementation instead of one per call site.
  • Requests must be cloned before modification; a request object is immutable.
  • Type the response generically and validate it at the boundary if the API is not under your control; a TypeScript assertion is not a runtime check.

FAQ

Service or shared component state?
A service when state must outlive one component or be shared between siblings — that is why stores are services. Component-local state stays in signals on the component itself.
How do I test a component that uses HttpClient?
Provide the testing backend instead of the real one: provideHttpClientTesting() plus HttpTestingController to assert the requests and flush canned responses.

Components and templates Routing and forms

Last refreshed 2026-09-18.