A service is where shared logic, state, or API access belongs instead of a component — Angular's dependency injection system handles creating (and by default, sharing) a single instance across every component that asks for it.
Generating a service with the CLI
ng generate service services/product
// shorthand
ng g s services/product
A basic service
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class ProductService {
constructor(private http: HttpClient) {}
getProducts(): Observable {
return this.http.get('/api/products');
}
getProduct(id: number): Observable {
return this.http.get(`/api/products/${id}`);
}
}
Using the service in a component
export class ProductListComponent implements OnInit {
products: Product[] = [];
constructor(private productService: ProductService) {}
ngOnInit() {
this.productService.getProducts().subscribe(products => {
this.products = products;
});
}
}
Angular's dependency injection resolves ProductService automatically just by declaring it as a constructor parameter type — no manual instantiation with new, and no manual wiring needed beyond the @Injectable decorator on the service itself.
Why providedIn: 'root' matters
providedIn: 'root' registers the service with the application's root injector, making it a singleton shared across the entire app — every component that injects ProductService gets the exact same instance, which is what allows a service to hold shared state (like a cached list of products) that stays consistent across every component using it.
A service holding shared, reactive state
@Injectable({ providedIn: 'root' })
export class CartService {
private itemsSubject = new BehaviorSubject([]);
items$ = this.itemsSubject.asObservable();
addItem(item: CartItem): void {
const current = this.itemsSubject.value;
this.itemsSubject.next([...current, item]);
}
get itemCount(): number {
return this.itemsSubject.value.length;
}
}
// in any component, anywhere in the app
this.cartService.items$.subscribe(items => this.cartItems = items);
A BehaviorSubject-backed service like this is a common lightweight pattern for sharing reactive state (a shopping cart, the current logged-in user) across unrelated components, without needing a full state management library for simpler cases.
Scoping a service to a specific module or component instead of the whole app
@Component({
selector: 'app-product-editor',
providers: [DraftService], // a fresh instance created just for this component
})
export class ProductEditorComponent {}
Listing a service in a component's own providers array (instead of using providedIn: 'root') creates a new instance scoped just to that component and its children — the right choice for state that genuinely shouldn't be shared app-wide, like a form's in-progress draft data that should reset each time the component is created fresh.