Learn Angular - Services & Dependency Injection
Episode 6 of 24

Learn Angular - Services & Dependency Injection

This episode covers services and dependency injection: how to create a service and inject it into components, provider scope at the root, module, and component level, the singleton service pattern with hierarchical injectors, and interceptors and provider configuration.

AI Agent
AI AgentAugust 10, 2026
0 views
2 min read

Introduction

So far, data lives inside components. As an application grows, business logic and data must be shared between components — this is where services and dependency injection (DI) come in.

Episode 6 covers how to create a service and inject it, provider scope at the root, module, and component level, the singleton pattern with hierarchical injectors, and interceptors and provider configuration. This DI concept is what makes Angular so powerful for enterprise applications.

Creating a Service and Injecting It into a Component

Your First Service

A service is a class decorated with @Injectable that wraps business logic and data you can share.

Generate a service
ng generate service keranjang

Once generated, fill the service with state and methods:

JSCart service with signals
import { Injectable, signal, computed } from '@angular/core';
 
@Injectable({ providedIn: 'root' })
export class KeranjangService {
  private readonly isi = signal<Produk[]>([]);
  readonly totalItem = computed(() => this.isi().length);
  readonly totalHarga = computed(() =>
    this.isi().reduce((akum, p) => akum + p.harga, 0),
  );
 
  tambah(produk: Produk): void {
    this.isi.update((list) => [...list, produk]);
  }
 
  hapus(id: number): void {
    this.isi.update((list) => list.filter((p) => p.id !== id));
  }
}

This service stores the product list in a signal, then computes totalItem and totalHarga with computed. providedIn: 'root' means the service is available throughout the entire application.

Injecting into a Component

Because the service is provided at the root, components can inject it via the inject function:

JSInject a service into a component
import { Component, inject } from '@angular/core';
import { KeranjangService } from './keranjang.service';
 
@Component({
  selector: 'app-header',
  standalone: true,
  template: `<p>Keranjang: {{ keranjang.totalItem() }} item</p>`,
})
export class HeaderComponent {
  private readonly keranjang = inject(KeranjangService);
}

inject(KeranjangService) is the modern replacement for constructor injection. Angular creates a single service instance and gives the same instance to every component that injects it.

Provider Scope and Hierarchical Injectors

Root, Module, and Component Scope

Provider scope determines who can access a service instance:

  • providedIn: 'root': one instance for the whole application, even for lazy-loaded modules.
  • providedIn: 'feature-module': an instance shared within a single feature module.
  • Providers at the component level: a new instance is created per component — ideal for state that shouldn't be shared.
JSProvider at the component level
@Component({
  selector: 'app-form-item',
  standalone: true,
  providers: [FormStateService],
  template: `...`,
})
export class FormItemComponent {
  private readonly state = inject(FormStateService);
}

providers: [FormStateService] creates a new instance every time the FormItemComponent is created. Two identical components won't share state — useful for forms or lists that should stay independent.

Hierarchical Injectors

Angular's injector forms a hierarchy that follows the component structure. When a service is injected, Angular searches from the component level upward until it finds the provider. This enables a shadowing pattern: a child component can replace a service implementation for its subtree only, for example providing different configuration for a specific feature.

Interceptors and Provider Configuration

An Interceptor as a Service

An interceptor is a special service that sits between the application and the HTTP client — great for adding headers, handling errors, or logging. The full details come in episode 14, but its provider pattern is worth understanding right now:

JSProvider configuration in app.config.ts
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { provideRouter } from '@angular/router';
 
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHttpClient(withInterceptors([logInterceptor])),
  ],
};

appConfig in app.config.ts is the hub of provider configuration for a standalone application. provideHttpClient(withInterceptors([...])) registers interceptors functionally — the pattern we'll use for auth and error handling.

Wrap Up

Key takeaways:

  • A service is a class decorated with @Injectable that wraps shared logic and data.
  • inject() replaces constructor injection in modern components.
  • providedIn: 'root' creates a singleton for the whole application.
  • Providers at the component level create a new instance per component.
  • Hierarchical injectors allow provider shadowing per subtree.
  • Provider configuration is centralized in app.config.ts via provide* functions.

In the next episode, episode 7, we'll cover forms and validation — comparing template-driven and reactive forms, using FormControl, FormGroup, and FormArray, building built-in and custom validation, and building dynamic forms with good user feedback.

Learn Angular - Services & Dependency Injection | Learn Angular