Service Levels in Angular
Source: Dev.to
Root-Level Services: The Global MVP 🌍
When you generate a service with the Angular CLI (ng generate service), the default looks like this:
@Injectable({
providedIn: 'root'
})
export class UserService { }
Angular creates one single instance of this service and shares it across the entire application.
Think of it as a front‑desk clerk in a hotel: every component talks to the same clerk and receives the same information.
Typical use cases
- Authentication Service – share login state across all components
- Configuration Service – global app settings that don’t change
- HTTP/API Service – a single point for backend communication
- State Management – shared data needed by multiple components
Root‑level services are the go‑to choice for most scenarios because they are simple, efficient, and avoid unnecessary instances.
Component-Level Services: Keep It Local 🏠
If a service should be used only by a single component—or each instance of a component needs its own copy—you can provide it at the component level:
@Component({
selector: 'app-shopping-cart',
templateUrl: './shopping-cart.component.html',
providers: [CartService]
})
export class ShoppingCartComponent { }
Each time the component is created, Angular creates a new instance of CartService for that component only.
Analogy: a private caretaker for each hotel room; Room 101 has its own caretaker, Room 102 has another, and they don’t share information.
When to use
- Form State – each form component keeps its own data
- Component‑Specific Logic – data that must not leak to other components
- Multiple Instances – the same component appears multiple times on a page and needs independent behavior
Example: a ProductFilterComponent displayed on several pages, each remembering its own filters.
Module-Level Services: The Middle Ground ⚖️
If you’re using Angular modules (instead of standalone components), you can provide services at the module level:
@NgModule({
declarations: [...],
imports: [...],
providers: [AuthGuardService]
})
export class AdminModule { }
Eagerly Loaded Module
When the module loads at application start, the service behaves like a singleton across the whole app—effectively the same as providedIn: 'root'.
Lazy‑Loaded Module
When a module is lazy‑loaded (e.g., via a route), Angular creates a separate injector for that module, giving the service a new instance scoped to the lazy‑loaded feature.
const routes: Routes = [
{ path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }
];
In this case, AdminService exists only within the lazy‑loaded admin module and is destroyed when the module is unloaded.
Which One Should You Use? 🤔
| Question | Recommended Scope |
|---|---|
| Is the service needed everywhere? | Root‑Level ✅ |
| Is the service used by only one component? | Component‑Level ✅ |
| Is the service scoped to a feature module that lazy‑loads? | Module‑Level ✅ |
Understanding Angular’s Dependency Injection hierarchy—how injectors resolve dependencies up the component tree—helps you decide the appropriate level.
The Bottom Line 💡
- Root‑level: One instance, shared everywhere.
- Component‑level: New instance per component.
- Module‑level: Instance depends on eager vs. lazy loading.
Choosing the right level keeps your app maintainable and performant; the wrong choice can make debugging a headache.