Services and Dependency Injection
Sharing logic and state across components with injectable services and Angular's dependency injection system.
2 menit membaca
Not everything belongs inside a component. Fetching data, managing authentication state, logging — logic that several unrelated components need — belongs in a service: a plain class whose job is to do one thing well, independent of any particular piece of UI.
Defining a service
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CartService {
private items: string[] = [];
addItem(productId: string): void {
this.items.push(productId);
}
getItems(): string[] {
return [...this.items];
}
getCount(): number {
return this.items.length;
}
}@Injectable marks the class as something Angular's dependency injection system knows how to create and hand out. providedIn: 'root' registers it as a single, application-wide instance — every component that asks for CartService gets the exact same object, which is what makes it useful for sharing state across components that otherwise have no relationship to each other.
Using a service in a component
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';
@Component({
selector: 'app-product-card',
template: `
<button (click)="addToCart()">Add to cart ({{ cart.getCount() }})</button>
`,
})
export class ProductCardComponent {
cart = inject(CartService);
addToCart(): void {
this.cart.addItem('sku-123');
}
}inject(CartService) asks Angular's injector for the shared CartService instance and hands it back — this is dependency injection: the component declares what it needs, and Angular is responsible for constructing and supplying it. The component never calls new CartService() itself, which matters because it means Angular (not the component) controls the instance's lifetime and can swap in a different implementation, such as a mock version in a test.
The older, equivalent way to do the same thing is constructor injection:
export class ProductCardComponent {
constructor(private cart: CartService) {}
}Both work identically — inject() is newer and tends to read more clearly outside a constructor, such as when initializing a class field, but you'll see constructor injection throughout existing Angular code and either is fine to use.
Why not just import a shared object?
You could export a plain object from a module and import it everywhere instead of using a service. The difference is control: Angular's injector can provide a different CartService at different levels of the app (a feature module could override it with a specialized version), can inject the service's own dependencies automatically (a service can itself inject() other services, forming a tree), and makes it straightforward to substitute a fake implementation in tests without touching the component under test at all.
A rule of thumb
If a piece of logic doesn't need a template — it doesn't render anything, it just computes, fetches, or stores — it's a candidate for a service rather than living inside a component. Keeping that separation is a large part of what keeps an Angular component "dumb" (focused on presentation) as an app grows.