Creating and Injecting Services
Services are ordinary TypeScript classes used to encapsulate logic and state that shouldn't live directly inside a component: fetching data from a backend, sharing state across unrelated components, wrapping browser APIs, or holding business logic that needs to be tested independently of any UI. Angular's dependency injection (DI) system creates and hands out instances of these classes automatically, so consuming code never has to call new Service() manually — it simply declares what it needs and Angular supplies it.
Cricket analogy: A service is like a team's dedicated statistician who tracks every run and wicket independently of the players on the field, and Angular's DI is like the team manager assigning that statistician to whichever match needs the data, without players hiring one themselves.
Defining a service
A service is created like any class, but decorated with @Injectable() so Angular's compiler knows to generate the metadata needed to construct it and resolve its own dependencies. The providedIn: 'root' option registers the service with the application's root injector, making it a singleton available anywhere in the app without needing to list it in a component's providers array.
Cricket analogy: @Injectable() is like the BCCI certifying a coach so any franchise can legally hire them without re-vetting credentials, and providedIn: 'root' is like that coach being contracted centrally so every IPL team shares the same one instead of hiring separately.
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CartService {
private readonly _items = signal<string[]>([]);
readonly items = this._items.asReadonly();
add(item: string): void {
this._items.update(current => [...current, item]);
}
clear(): void {
this._items.set([]);
}
}Injecting a service
There are two idiomatic ways to obtain a service instance in a component or another service: constructor injection, where the service type is declared as a constructor parameter, and the inject() function, which can be called at field-initializer time or inside a constructor without needing a parameter. Angular resolves the request by walking up the injector hierarchy starting from where the consumer lives until it finds a provider that matches.
Cricket analogy: Constructor injection is like a new batsman walking in and the umpire already handing them the guard-marking equipment as part of the ritual, while inject() is like a fielder grabbing sunglasses from the kit bag mid-over without needing that formal handoff.
import { Component, inject } from '@angular/core';
import { CartService } from './cart.service';
@Component({
selector: 'app-cart-summary',
standalone: true,
template: `<p>{{ cart.items().length }} items</p>`,
})
export class CartSummaryComponent {
// inject() style (common in standalone components)
protected cart = inject(CartService);
}
// Equivalent constructor-injection style:
// constructor(protected cart: CartService) {}A service registered with providedIn: 'root' is tree-shakable: if no code in the application ever injects it, Angular's build process can exclude it from the final bundle entirely — something that isn't possible with services registered only via an NgModule's providers array, which are always included once the module is loaded.
Forgetting @Injectable() on a service that itself has constructor dependencies is a common early mistake — TypeScript's decorator metadata (needed for Angular to know the constructor parameter types at runtime) is only emitted for classes decorated with @Injectable(), @Component(), or @Directive(). A service with no dependencies of its own may appear to work without the decorator, but it should still always be added for consistency and future-proofing.
Services can themselves depend on other services simply by injecting them the same way, and Angular resolves the entire dependency graph automatically and lazily — each service is instantiated only the first time it (or something depending on it) is actually requested, and providedIn: 'root' singletons are cached and reused for the lifetime of the application.
Cricket analogy: It's like a fast bowler needing a fitness trainer, who in turn needs a physiotherapist, and the franchise only actually books the physio the first time a bowler gets injured, then reuses that same physio for the rest of the season.
- Services are plain classes decorated with @Injectable() to allow Angular's DI system to construct and supply them.
- providedIn: 'root' registers a service as an application-wide singleton and makes it tree-shakable if unused.
- Services can be injected via constructor parameters or the inject() function.
- Services commonly hold shared state, data-fetching logic, or reusable business logic outside of components.
- Angular instantiates services lazily, only when first requested, and caches root-provided singletons for reuse.
- Services can depend on other services; Angular resolves the whole dependency graph automatically.
Practice what you learned
1. What does the @Injectable() decorator enable?
2. What effect does providedIn: 'root' have on a service?
3. Which two are valid, idiomatic ways to obtain a service instance in a component?
4. When is a providedIn: 'root' service instantiated?
5. Why might omitting @Injectable() on a service cause runtime issues?
Was this page helpful?
You May Also Like
The @Injectable Decorator and Providers
Explore the @Injectable decorator in depth and the different ways to configure providers — class, value, factory, and existing — to control what Angular's DI system supplies.
Hierarchical Dependency Injection
Understand how Angular's injectors form a tree mirroring the component hierarchy, how token resolution walks up that tree, and how to scope service instances deliberately.
The inject() Function
Learn how the inject() function offers a flexible alternative to constructor injection, and how it enables DI in functional contexts like guards, resolvers, and interceptors.
Introduction to Signals
Understand Angular Signals — the reactive primitive that wraps a value and notifies dependents on change, forming the foundation of Angular's modern change-detection model.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics