Services
Dependency Injection
Master Angular Dependency Injection, covering the injector hierarchy, provider registration, injection tokens, and the inject() function.
1. Learning Objectives
In this lesson, you will master Angular's dependency injection system. By the end of this topic, you will be able to:
- Explain the purpose and benefits of dependency injection (DI).
- Register providers at different levels of the injector hierarchy.
- Use the
inject()function and constructor injection to obtain dependencies. - Create custom
InjectionTokeninstances for non-class dependencies. - Understand the difference between
providedIn: 'root'and component-level providers.
2. Overview
Dependency injection is a design pattern where a class receives its dependencies from an external source rather than creating them internally. Angular has a built-in DI framework that manages the creation and delivery of dependencies throughout the application. When a component or service declares a dependency, Angular's injector locates the appropriate provider, creates an instance (or reuses an existing one), and delivers it automatically.
3. Why This Topic Matters
Dependency injection is foundational to every Angular application:
- Testability: Without DI, components create their own dependencies internally using
new, making it impossible to swap them with mock implementations during unit tests. DI allows you to inject test doubles effortlessly. - Unintended Singleton Sharing: Registering a service with
providedIn: 'root'creates a single shared instance across the entire application. If a component needs its own isolated instance (e.g., a form state manager), you must register the provider at the component level instead.
4. Real-World Analogy
Think of dependency injection like **a hospital staffing system**:
- Without DI (Self-Staffing): Each department hires its own doctors, nurses, and technicians. This leads to duplication (every department has its own pharmacist) and makes it hard to swap staff during emergencies.
- With DI (Central HR Department): A central HR office (the Injector) maintains a registry of all staff members (Providers). When a department (Component) needs a surgeon, it requests one from HR, which assigns an available surgeon from the registry.
- Singleton vs Scoped (Shared vs Dedicated Staff): Some staff members are shared across the hospital (root-level singleton, like the head pharmacist). Others are assigned exclusively to one department (component-level provider, like a dedicated ward nurse).
5. Core Concepts
Angular's DI system uses three key building blocks:
- Injector: An object that maintains a container of provider-instance mappings. Angular creates a hierarchy of injectors: a root injector (application-wide) and optional child injectors (per component or module).
- Provider: A configuration object that tells the injector how to create an instance of a dependency. Providers can supply classes, values, factories, or existing instances.
- Token: A key used to look up a provider in the injector. For class-based services, the class itself serves as the token. For non-class values (like configuration objects), you use an
InjectionToken.
| Registration Level | Scope | Instance Lifetime |
|---|---|---|
| providedIn: 'root' | Application-wide singleton | Lives for the entire application lifecycle. |
| Component providers | Component and its children | Created when the component renders, destroyed when it is removed. |
| Route providers | Route and its child routes | Created when the route activates, destroyed when the user navigates away. |
6. Syntax & API Reference
Below are the main patterns for registering and injecting dependencies:
7. Visual Diagram
This diagram displays the injector hierarchy and provider resolution:
8. Live Example — Full Working Code
Below is a complete example showing provider types — useClass, useValue, and useFactory:
9. Interactive Playground
Try It Yourself Challenges:
- Register a service at the component level and verify that two instances of the component each get their own service instance.
- Create an
InjectionTokenfor a feature flag boolean and inject it into a component.
10. Common Mistakes
| Mistake | Why it happens | Wrong | Correct |
|---|---|---|---|
| Missing @Injectable decorator | Creating a service class without the @Injectable() decorator. Angular cannot resolve constructor dependencies for classes that lack this decorator. |
export class MyService { } |
@Injectable({ providedIn: 'root' }) export class MyService { } |
| Using new to create service instances | Manually instantiating a service with new MyService() bypasses the injector entirely. The instance will not have its own dependencies injected. |
this.svc = new MyService(); |
private svc = inject(MyService); |
11. Best Practices
- Default to providedIn: 'root': For most services, register with
providedIn: 'root'. This enables tree-shaking (unused services are removed from the bundle) and creates a single shared instance. - Use inject() over constructor injection: The modern
inject()function is simpler, works in functions and guards, and does not require constructor parameter declarations. - Use InjectionToken for non-class values: Never use plain strings as DI tokens. Use
InjectionTokeninstances with a descriptive name and generic type parameter for type safety. - Scope services intentionally: Only use component-level providers when you explicitly need a new instance per component. Unnecessary scoping defeats singleton sharing and increases memory usage.
12. Browser Compatibility/Requirements
Dependency injection is an Angular framework feature implemented entirely in TypeScript. It has no browser-specific requirements and works consistently across all modern browsers.
13. Interview Questions
Q1: What is the difference between providedIn: 'root' and registering a provider in a component's providers array?
Answer: providedIn: 'root' registers the service at the root injector, creating a single shared instance (singleton) for the entire application. It also enables tree-shaking. Registering in a component's providers array creates a new instance for that component and its children. Each instance is destroyed when the component is removed from the DOM.
Q2: What is an InjectionToken and when do you need one?
Answer: An InjectionToken is a unique DI token used for non-class dependencies (like configuration objects, feature flags, or API URL strings). You need it because TypeScript interfaces and primitive values cannot serve as DI tokens at runtime — they are erased during compilation. InjectionToken provides a stable runtime reference.
14. Debugging Exercise
The application throws "NullInjectorError: No provider for AnalyticsService". Identify the bug:
Diagnosis: The AnalyticsService class is missing the @Injectable() decorator and has no provider registration. Angular's injector does not know how to create or locate this service.
Fix (choose one):
15. Practice Exercises
Exercise 1: Build a theme configuration system
Create an InjectionToken<ThemeConfig> with properties for primary color, font family, and dark mode flag. Register a default value at bootstrap. Inject the token into a component and apply the theme values to the template.
16. Scenario-Based Challenge
The Multi-Tenant Logger Challenge:
You are building a multi-tenant application where each tenant module needs its own logger instance with a custom prefix (e.g., "[TenantA]" or "[TenantB]"). Design a useFactory provider that reads the tenant name from a configuration token and creates a correctly prefixed logger instance for each tenant module.
17. Quick Quiz
Q1: Which decorator must be applied to a service class for Angular's DI system to resolve its dependencies?
A) @Component
B) @Injectable
C) @Inject
Answer: B — The @Injectable() decorator marks a class as available for dependency injection.
18. Summary & Key Takeaways
- Angular's DI framework manages dependency creation and delivery through an injector hierarchy.
- Use
providedIn: 'root'for application-wide singletons with tree-shaking support. - Use component-level providers when each component instance needs its own service instance.
- Use
InjectionTokenfor non-class dependencies like configuration objects and primitive values. - Prefer the
inject()function over constructor injection for simpler, more flexible code.
19. Cheat Sheet
| Provider Type | Purpose |
|---|---|
useClass |
Provide an alternative class implementation for a token. |
useValue |
Provide a static value (object, string, number) for a token. |
useFactory |
Provide a value created by a factory function at runtime. |
useExisting |
Alias one token to an existing provider. |