Reader Stacks

Creating Services in Angular

A service is a plain class Angular's dependency injector knows how to construct and share — the mechanism for putting logic and data outside any one component.

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 { /* ... */ }
Topics: Developer Productivity APIs & Integrations