0
0
Angularframework~15 mins

How dependency injection works in Angular - Mechanics & Internals

Choose your learning style9 modes available
Overview - How dependency injection works in Angular
What is it?
Dependency Injection (DI) in Angular is a way to provide components or services with the things they need, called dependencies, without creating them inside the components. Instead of making a component build or find its own dependencies, Angular's DI system gives them automatically. This helps keep code clean, easy to test, and organized. DI works by registering providers that tell Angular how to create or find these dependencies.
Why it matters
Without DI, every component would have to create or manage its own dependencies, leading to repeated code and tight coupling. This makes apps harder to maintain and test. DI solves this by centralizing how dependencies are created and shared, making apps more flexible and easier to change. It also allows swapping implementations without changing the components that use them, which is crucial for large, real-world applications.
Where it fits
Before learning Angular DI, you should understand basic Angular components and services. After mastering DI, you can learn advanced topics like hierarchical injectors, lazy loading with DI, and testing Angular apps using mocks and spies.
Mental Model
Core Idea
Angular's dependency injection is a system that automatically supplies components and services with the objects they need, so they don't have to create or find them themselves.
Think of it like...
Imagine a coffee shop where baristas don’t have to go to the storage room to get coffee beans or milk; instead, a helper brings these ingredients to them whenever needed. This helper is like Angular’s DI system, delivering what each barista (component) needs without them leaving their station.
┌───────────────┐       ┌───────────────┐       ┌───────────────┐
│   Component   │──────▶│   Injector    │──────▶│   Provider    │
│ (Needs Dep.)  │       │ (Finds Dep.)  │       │ (Creates Dep.)│
└───────────────┘       └───────────────┘       └───────────────┘
       ▲                      │                        │
       │                      │                        │
       └──────────────────────┴────────────────────────┘
                 Angular DI supplies dependencies
Build-Up - 8 Steps
1
FoundationWhat is Dependency Injection
🤔
Concept: Introduce the basic idea of DI as a way to supply objects a class needs from outside rather than creating them inside.
Dependency Injection means giving a component or service the things it needs (dependencies) from outside instead of making them itself. For example, if a component needs a logging service, DI will provide it instead of the component creating it. This helps keep code clean and easy to change.
Result
You understand DI as a concept that separates creating dependencies from using them.
Understanding DI as a way to separate creation and use of dependencies is the foundation for grasping Angular’s powerful system.
2
FoundationAngular Services as Dependencies
🤔
Concept: Learn that Angular services are common dependencies injected into components to share logic or data.
In Angular, services are classes that hold logic or data to share across components. Instead of each component making its own service, Angular DI provides the same service instance to any component that asks for it. This sharing is done by registering the service as a provider.
Result
You see services as reusable pieces that Angular DI can supply to components.
Recognizing services as injectable dependencies helps you see why DI is essential for code reuse and organization.
3
IntermediateHow Angular Injectors Work
🤔Before reading on: do you think Angular creates a new instance of a service every time a component asks for it, or does it reuse instances? Commit to your answer.
Concept: Explain Angular’s injector hierarchy and how it decides which instance to provide.
Angular uses injectors arranged in a tree. The root injector creates singleton instances shared app-wide. Child injectors can create new instances for specific parts of the app. When a component asks for a dependency, Angular looks up the injector tree to find the closest provider. This means some services are shared globally, others locally.
Result
You understand that Angular reuses service instances by default but can create new ones in child injectors.
Knowing the injector hierarchy clarifies how Angular manages service lifetimes and scopes, which is key for designing scalable apps.
4
IntermediateRegistering Providers in Angular
🤔Before reading on: do you think Angular automatically knows how to create every service, or do you need to tell it how? Commit to your answer.
Concept: Learn how to tell Angular how to create dependencies by registering providers in modules or components.
To use DI, Angular needs to know how to create a dependency. This is done by registering providers. You can register a service in the root module with 'providedIn: root' or in component providers array. Providers tell Angular what class or value to use when injecting. This registration controls scope and lifetime.
Result
You can control where and how Angular creates service instances by registering providers.
Understanding provider registration is crucial because it controls dependency scope and sharing, affecting app behavior and performance.
5
IntermediateInjecting Dependencies in Components
🤔
Concept: Show how to request dependencies in a component using constructor parameters and Angular’s DI system.
In Angular, you inject dependencies by adding them as parameters to a component’s constructor. Angular sees these parameters and uses the injector to supply the matching instances automatically. For example, constructor(private logger: LoggerService) tells Angular to inject LoggerService.
Result
Components receive dependencies automatically without manual creation.
Knowing that constructor parameters trigger DI helps you write clean, testable components without manual wiring.
6
AdvancedHierarchical Injectors and Scoped Services
🤔Before reading on: do you think a service provided in a child component’s providers array is shared with sibling components or unique to that component? Commit to your answer.
Concept: Explore how Angular’s hierarchical injectors allow different instances of the same service in different parts of the app.
Angular’s injectors form a tree matching the component tree. If a service is provided in a child component’s providers array, Angular creates a new instance just for that component and its children. Siblings get different instances. This allows fine control over service scope and state isolation.
Result
You can create multiple instances of a service scoped to parts of the app.
Understanding hierarchical injectors unlocks advanced patterns like local state management and lazy-loaded module isolation.
7
AdvancedUsing Injection Tokens for Non-Class Dependencies
🤔
Concept: Learn how to inject values or interfaces using InjectionToken when classes are not available.
Sometimes you want to inject a value, config, or interface instead of a class. Angular uses InjectionToken to create a key for such dependencies. You register a provider with this token and a value. Then you inject it by specifying the token in the constructor with @Inject(token). This expands DI beyond classes.
Result
You can inject any kind of dependency, not just classes.
Knowing InjectionTokens broadens DI’s power to include configs and abstractions, making apps more flexible.
8
ExpertHow Angular Resolves Dependencies at Runtime
🤔Before reading on: do you think Angular creates all dependencies at app start or only when needed? Commit to your answer.
Concept: Deep dive into Angular’s runtime process for resolving and instantiating dependencies lazily and efficiently.
Angular builds a map of providers at compile time. At runtime, when a component requests a dependency, Angular’s injector looks up the provider in the injector tree. It creates the instance only if it doesn’t exist yet (lazy instantiation). It also handles circular dependencies by throwing errors. This runtime resolution is optimized for speed and memory.
Result
You understand Angular’s DI is lazy, hierarchical, and optimized for performance.
Knowing Angular’s runtime DI mechanics helps debug complex injection issues and optimize app startup.
Under the Hood
Angular’s DI system uses a hierarchical injector tree matching the component tree. Each injector holds a map of providers. When a dependency is requested, Angular starts from the requesting component’s injector and moves up the tree until it finds a provider. It then creates or returns the existing instance. Providers can be classes, values, or factories. Angular compiles metadata to build this map ahead of time, enabling fast runtime lookup and lazy instantiation.
Why designed this way?
Angular’s DI was designed to support modular, scalable apps with clear dependency boundaries. The hierarchical injector allows different parts of the app to have isolated or shared dependencies, supporting lazy loading and feature modules. This design avoids global singletons everywhere, which can cause tight coupling and hard-to-debug state sharing. Alternatives like manual wiring or service locators were rejected for being error-prone and less maintainable.
App Injector Tree

┌───────────────┐
│ Root Injector │
│ (Singletons)  │
└──────┬────────┘
       │
┌──────▼────────┐
│ Feature Module│
│ Injector      │
└──────┬────────┘
       │
┌──────▼────────┐
│ Component A   │
│ Injector      │
└───────────────┘

Dependency Request Flow:
Component A → Component Injector → Feature Module Injector → Root Injector

Angular returns the first matching provider instance found.
Myth Busters - 4 Common Misconceptions
Quick: Does Angular create a new instance of a service every time it is injected? Commit to yes or no.
Common Belief:Angular creates a new instance of a service every time it is injected into a component.
Tap to reveal reality
Reality:Angular creates a single instance per injector by default and reuses it for all injections within that injector’s scope.
Why it matters:Believing this leads to unnecessary code complexity and misunderstanding of service state sharing, causing bugs in stateful services.
Quick: Can you inject a simple string or object directly without special setup? Commit to yes or no.
Common Belief:You can inject any value like a string or object directly without extra configuration.
Tap to reveal reality
Reality:To inject non-class values, you must use InjectionToken and register a provider for it.
Why it matters:Ignoring this causes runtime errors and confusion when trying to inject configs or constants.
Quick: If a service is provided in a child component, do sibling components share the same instance? Commit to yes or no.
Common Belief:All components in the app share the same instance of a service regardless of where it is provided.
Tap to reveal reality
Reality:Services provided in a child component’s providers array are unique to that component and its children, not shared with siblings.
Why it matters:Misunderstanding this leads to unexpected behavior and bugs due to unintended shared or isolated state.
Quick: Does Angular DI automatically resolve circular dependencies without errors? Commit to yes or no.
Common Belief:Angular DI can handle circular dependencies between services without any problem.
Tap to reveal reality
Reality:Angular throws an error if circular dependencies exist because it cannot resolve which to create first.
Why it matters:Assuming circular dependencies work causes runtime crashes and hard-to-debug errors.
Expert Zone
1
Angular’s injector hierarchy allows lazy-loaded modules to have their own injector, enabling multiple instances of services with the same class but different lifetimes.
2
Using 'providedIn: root' in services enables tree-shaking, so unused services are removed from the final bundle, improving app size.
3
InjectionTokens can be multi-providers, allowing multiple values to be injected as an array, which is useful for plugin or extension patterns.
When NOT to use
Avoid Angular DI for very simple apps where manual wiring is easier and clearer. Also, for performance-critical code paths, excessive DI can add overhead; in such cases, direct instantiation or factory patterns might be better. For cross-framework or non-Angular code, use other patterns like service locators or context passing.
Production Patterns
In production, Angular DI is used to provide singleton services for app-wide state, scoped services for feature modules, and mock services during testing. Lazy-loaded modules use their own injectors to isolate dependencies. InjectionTokens are used for configuration and environment variables. Multi-providers enable plugin architectures.
Connections
Inversion of Control (IoC)
Angular DI is a practical implementation of the IoC principle.
Understanding IoC helps grasp why DI reverses control of dependency creation, making components simpler and more modular.
Service Locator Pattern
DI and Service Locator both provide dependencies but DI is declarative and safer.
Knowing the difference helps avoid anti-patterns and choose DI for better testability and maintainability.
Supply Chain Management
Both manage how needed items are delivered efficiently to where they are used.
Seeing DI as a supply chain clarifies the importance of timely, scoped delivery of dependencies to components.
Common Pitfalls
#1Registering a service only in a child component expecting it to be singleton app-wide.
Wrong approach:@Component({ providers: [MyService] }) export class ChildComponent {} // Other components expect same instance but get different ones.
Correct approach:@Injectable({ providedIn: 'root' }) export class MyService {} // Registered globally for singleton use.
Root cause:Misunderstanding Angular’s hierarchical injectors and provider scopes.
#2Trying to inject a configuration object directly without using InjectionToken.
Wrong approach:constructor(private config: Config) {} // Config is an interface or object, no provider registered.
Correct approach:export const CONFIG_TOKEN = new InjectionToken('config'); providers: [ { provide: CONFIG_TOKEN, useValue: { apiUrl: '...' } } ] constructor(@Inject(CONFIG_TOKEN) private config: Config) {}
Root cause:Not using InjectionToken for non-class dependencies.
#3Injecting dependencies without adding them to constructor parameters.
Wrong approach:export class MyComponent { private logger: LoggerService; ngOnInit() { this.logger.log('Hello'); } } // LoggerService never injected, undefined error.
Correct approach:constructor(private logger: LoggerService) {} ngOnInit() { this.logger.log('Hello'); }
Root cause:Not understanding that Angular injects dependencies via constructor parameters.
Key Takeaways
Angular’s dependency injection system automatically provides components and services with the objects they need, improving code organization and testability.
The injector hierarchy controls the scope and lifetime of service instances, allowing both shared singletons and isolated instances.
Providers must be registered to tell Angular how to create dependencies, either globally or locally, affecting how and where instances are shared.
InjectionTokens enable injecting non-class dependencies like configuration values, expanding DI beyond just classes.
Understanding Angular’s DI runtime mechanism helps debug injection errors and optimize app performance.