Anything that shouldn't live inside a single component — an HTTP call, shared application state, a piece of business logic reused in several places — belongs in a service. Angular services are plain TypeScript classes; what makes them "Angular services" is the @Injectable decorator plus the framework's dependency injection system knowing how to construct and hand them out.
1. Generating a service
ng generate service product
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ProductService {
private products: Product[] = [];
getAll(): Product[] {
return this.products;
}
}
2. providedIn: 'root'
providedIn: 'root' registers the service with the application's root injector, which does two things at once: it makes the service available for injection anywhere in the app without listing it in any module's providers array, and it makes the service a singleton — one shared instance for the entire application's lifetime, not a new instance per component.
3. Injecting a service into a component
import { Component, inject } from '@angular/core';
import { ProductService } from './product.service';
@Component({ /* ... */ })
export class ProductListComponent {
private productService = inject(ProductService);
products = this.productService.getAll();
}
The inject() function is the current recommended style for standalone components; constructor injection (constructor(private productService: ProductService) {}) still works identically and appears throughout older code and tutorials — both resolve the same singleton instance.
4. Services for shared state
Because a providedIn: 'root' service is a genuine singleton, it's the natural place to hold state that multiple unrelated components need to read or write — a shopping cart, the logged-in user, a set of active filters — without wiring that data through a long chain of @Input/@Output bindings between components that aren't directly related in the template tree.
@Injectable({ providedIn: 'root' })
export class CartService {
private items: CartItem[] = [];
add(item: CartItem): void {
this.items.push(item);
}
getItems(): CartItem[] {
return this.items;
}
}
Any component that injects CartService gets the same underlying array — a component adding an item and a completely unrelated header component displaying the cart count are both reading and writing the same shared instance.
5. Scoping a service to one component instead
Listing a service in a specific component's own providers array, rather than using providedIn: 'root', creates a new instance scoped to that component and its children instead of one shared app-wide instance — useful when the state genuinely shouldn't leak beyond one feature (a multi-step form's in-progress data, for example).
@Component({
providers: [WizardStateService],
})
export class CheckoutWizardComponent { /* ... */ }