Bird
Raised Fist0
Angularframework~15 mins

How dependency injection works in Angular - Mechanics & Internals

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of dependency injection in Angular?
easy
A. To write all code inside a single file
B. To manually create instances of services inside components
C. To provide components with the services they need automatically
D. To prevent components from using any services

Solution

  1. Step 1: Understand dependency injection concept

    Dependency injection automatically provides required services to components without manual creation.
  2. Step 2: Compare options with this concept

    Only To provide components with the services they need automatically describes automatic provision of services, which is the core of dependency injection.
  3. Final Answer:

    To provide components with the services they need automatically -> Option C
  4. Quick Check:

    Dependency injection = automatic service provision [OK]
Hint: Dependency injection means Angular gives services automatically [OK]
Common Mistakes:
  • Thinking you must create service instances manually
  • Believing dependency injection restricts service use
  • Confusing dependency injection with code organization
2. Which decorator makes a class injectable in Angular?
easy
A. @Component
B. @Input
C. @NgModule
D. @Injectable

Solution

  1. Step 1: Identify Angular decorators for injection

    The decorator @Injectable marks a class as available for dependency injection.
  2. Step 2: Eliminate other decorators

    @Component is for components, @NgModule for modules, and @Input for input properties, none make a class injectable.
  3. Final Answer:

    @Injectable -> Option D
  4. Quick Check:

    @Injectable = makes class injectable [OK]
Hint: Use @Injectable to mark services for injection [OK]
Common Mistakes:
  • Confusing @Component with @Injectable
  • Using @NgModule to make services injectable
  • Forgetting to add @Injectable to services
3. Given this Angular service and component code, what will be logged?
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class LoggerService {
  log(message: string) {
    console.log('Log:', message);
  }
}

import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({ selector: 'app-root', template: '' })
export class AppComponent {
  constructor(private logger: LoggerService) {
    this.logger.log('Hello');
  }
}
medium
A. Log: Hello
B. Error: LoggerService not found
C. No output
D. Log: undefined

Solution

  1. Step 1: Understand service injection in constructor

    The LoggerService is injected into AppComponent via constructor and used to log a message.
  2. Step 2: Analyze the log call

    The log method prints 'Log:' plus the message 'Hello', so console shows 'Log: Hello'.
  3. Final Answer:

    Log: Hello -> Option A
  4. Quick Check:

    Injected service logs message correctly [OK]
Hint: Injected services work via constructor parameters [OK]
Common Mistakes:
  • Expecting error if service is providedIn root
  • Thinking log method won't run without template
  • Confusing console output with return value
4. What is wrong with this Angular service injection code?
import { Injectable } from '@angular/core';

@Injectable()
export class DataService {
  getData() { return [1, 2, 3]; }
}

import { Component } from '@angular/core';

@Component({ selector: 'app-data', template: '' })
export class DataComponent {
  constructor(private dataService: DataService) {}
}
medium
A. DataComponent constructor should not have parameters
B. DataService is not provided in any injector
C. DataService class is missing @Component decorator
D. getData method must be static

Solution

  1. Step 1: Check service provider configuration

    The service has @Injectable() but no providedIn or provider registration, so Angular doesn't know where to provide it.
  2. Step 2: Understand injection failure cause

    Without being provided, Angular throws an error when injecting DataService into DataComponent.
  3. Final Answer:

    DataService is not provided in any injector -> Option B
  4. Quick Check:

    Missing provider causes injection error [OK]
Hint: Always provide services via providedIn or providers array [OK]
Common Mistakes:
  • Assuming @Injectable alone provides the service
  • Adding @Component to services incorrectly
  • Thinking constructor parameters are optional for injection
5. You want to inject two different services of the same class but with different configurations in Angular. Which approach correctly achieves this?
hard
A. Use Angular injection tokens to provide different configurations and inject by token
B. Create two different classes extending the service and inject each separately
C. Use @Injectable({ providedIn: 'root' }) on the class and inject twice in constructor
D. Inject the service once and manually change its configuration before each use

Solution

  1. Step 1: Understand multiple instances need

    Injecting the same class twice with different configs requires distinguishing them in Angular's injector.
  2. Step 2: Use injection tokens for different configs

    Angular injection tokens allow providing multiple versions of the same class with different configurations and injecting them separately.
  3. Step 3: Evaluate other options

    Use @Injectable({ providedIn: 'root' }) on the class and inject twice in constructor injects the same instance twice, B duplicates classes unnecessarily, D is error-prone and not recommended.
  4. Final Answer:

    Use Angular injection tokens to provide different configurations and inject by token -> Option A
  5. Quick Check:

    Injection tokens enable multiple configured instances [OK]
Hint: Use injection tokens for multiple differently configured services [OK]
Common Mistakes:
  • Injecting same service twice without tokens
  • Creating duplicate service classes unnecessarily
  • Changing service config manually at runtime
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard