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.

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.
A service is a class decorated with @Injectable that wraps business logic and data you can share.
ng generate service keranjangOnce generated, fill the service with state and methods:
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.
Because the service is provided at the root, components can inject it via the inject function:
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 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.@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.
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.
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:
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.
Key takeaways:
@Injectable that wraps shared logic and data.inject() replaces constructor injection in modern components.providedIn: 'root' creates a singleton for the whole application.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.