Angular 54 🅰️ Dependency Injection in Depth
Angular’s dependency injection is the framework’s backbone. Every service, every component, every directive, and every pipe lives in an injector hierarchy, and the tokens, the providers, and the resolution rules determine what is created, when, and where. The basic usage — @Injectable({ providedIn: 'root' }), the constructor injection, the inject() function — is familiar. The advanced usage is where the framework’s flexibility lives: the hierarchical injectors, the provider scopes, the injection tokens, the useClass / useValue / useFactory / useExisting providers, the @Self, @SkipSelf, @Host, and @Optional decorators, the EnvironmentInjector and the ElementInjector, and the patterns that make the injection predictable. This chapter covers the DI in depth: the injector hierarchy, the provider types, the injection tokens, the resolution modifiers, the tree-shakable providers, and the testing patterns that come from the DI’s design.
Key point: Angular has two injector hierarchies: the EnvironmentInjector (the module injector, configured with the providers array or the providedIn on the @Injectable) and the ElementInjector (the element injector, configured with the providers on the component or the directive). The inject() function and the constructor injection both resolve from the current injector, and the resolution walks up the hierarchy. The provider types — useClass, useValue, useFactory, useExisting — determine what the injector returns for a token. The injection tokens — the class, the InjectionToken, the string — identify the dependency. The resolution modifiers — @Self, @SkipSelf, @Host, @Optional — control where the resolution stops and what happens when the token is not found. The providedIn: 'root' makes the provider tree-shakable, and the providedIn: 'any' makes it per-injector.
The injector hierarchy
Angular’s DI is hierarchical. There are two parallel hierarchies: the EnvironmentInjector and the ElementInjector. The two are separate, and the resolution order is the element injector first, then the environment injector.
The EnvironmentInjector. The environment injector is the module-level injector. It is configured by the @Injectable({ providedIn: 'root' }), the providers array of the ApplicationConfig, and the providers of the lazy-loaded routes. There is one root environment injector per application, and the child environment injectors for the lazy modules.
The ElementInjector. The element injector is the component- and directive-level injector. It is configured by the providers array on the @Component or the @Directive. Each element in the template has an element injector, and the injectors form a hierarchy that follows the DOM’s tree.
Why the two hierarchies are separate. The environment injector is for the application-wide services, and the element injector is for the component-scoped services. The two are separate because the component-scoped services should be created and destroyed with the component, and the application-wide services should live for the application’s lifetime.
Why the resolution order is the element first. The inject() and the constructor injection resolve from the current injector. The resolution walks up the element injector hierarchy first, and then the environment injector. The element injector’s provider wins over the environment injector’s, which is why a component can override a service for its subtree.
Why the hierarchy matters for the test. A test can provide a mock for the service at the component’s level, and the component’s subtree uses the mock. The environment injector’s provider is not used, which is the isolation.
Why the hierarchy matters for the lazy modules. A lazy-loaded module has its own environment injector, and the services provided there are the module’s. The services are created when the module is loaded, and they are the module’s lifetime.
Why the hierarchy can be confusing. The two hierarchies are the separate, and the resolution order is the element first. A service provided at the root and a service provided at the component are the different, and the same token can resolve to the different instances in the different parts of the tree.
Why the hierarchy is the DI’s power. The hierarchy is what makes the DI flexible. The root services are the application-wide, the module services are the feature-scoped, and the component services are the local. The same token can have the different instances in the different scopes, which is the override and the isolation.
The provider types
A provider is the recipe for the injector. The provider types — useClass, useValue, useFactory, useExisting — determine what the injector returns.
The useClass provider. The useClass tells the injector to instantiate the class.
providers: [
{ provide: Logger, useClass: ConsoleLogger },
]
The injector creates a ConsoleLogger when the Logger is requested. The useClass is the default when the provider is a class.
Why the useClass is the swap. The useClass is the way to swap the implementation. The token is the Logger, and the implementation is the ConsoleLogger. The alternative implementation — the FileLogger — is the different provider.
The useValue provider. The useValue tells the injector to return the value.
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
]
The injector returns the string when the API_URL is requested. The useValue is for the configuration values, the constants, and the test doubles.
Why the useValue is the constant. The useValue is the value, not the factory. The value is the same for all the consumers, and the injector does not create it.
The useFactory provider. The useFactory tells the injector to call the factory function.
providers: [
{
provide: Logger,
useFactory: (config: AppConfig) => config.debug ? new ConsoleLogger() : new NullLogger(),
deps: [AppConfig],
},
]
The injector calls the factory with the AppConfig and returns the result. The deps array declares the factory’s dependencies.
Why the useFactory is the dynamic. The useFactory is for the cases where the instance depends on the runtime values. The factory receives the dependencies and returns the instance, and the logic is the factory’s.
The useExisting provider. The useExisting tells the injector to return the existing instance for the alias token.
providers: [
{ provide: Logger, useClass: ConsoleLogger },
{ provide: OldLogger, useExisting: Logger },
]
The OldLogger is the alias for the Logger, and the injector returns the same instance. The two tokens resolve to the same object.
Why the useExisting is the alias. The useExisting is the way to create the alias. The two tokens resolve to the same instance, and the alias is the backward compatibility.
Why the useClass and the useExisting differ. The useClass creates the new instance, and the useExisting returns the existing. The useClass with the same class twice creates the two instances, and the useExisting shares the one.
The injection tokens
A token is the key that identifies the dependency. The class, the InjectionToken, and the string are the token types.
The class token. The class is the token, and the injector resolves the class’s type.
@Injectable({ providedIn: 'root' })
export class UserService {}
constructor(private userService: UserService) {}
The UserService is the token and the type, and the injector returns the instance.
Why the class is the token. The class is the natural token, and the type is the class. The pattern is the common, and the injector’s map is the class.
The InjectionToken. The InjectionToken is the token for the non-class dependencies.
export const API_URL = new InjectionToken<string>('API_URL');
export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
]
The InjectionToken<string> is the token, and the provide maps it to the value. The pattern is for the configuration, the constants, and the interfaces.
Why the InjectionToken is the typed. The InjectionToken<T> is the generic, and the inject(API_URL) is the string. The type is the safety, and the token is the key.
Why the InjectionToken is preferred over the string. The string token can collide with the other services, and the InjectionToken is the unique. The modern pattern is the InjectionToken, and the string is the legacy.
The providedIn: 'root'. The @Injectable({ providedIn: 'root' }) makes the provider tree-shakable and the singleton.
@Injectable({ providedIn: 'root' })
export class UserService {}
The service is the singleton, and the bundler can tree-shake the unused services. The pattern is the modern default.
Why the tree-shakable matters. The tree-shakable providers are the ones the bundler can remove when the service is not used. The providedIn: 'root' is the tree-shakable, and the providers array is not.
Why the providedIn can be the module. The providedIn: SomeModule makes the service the module’s scope. The service is the singleton within the module, and the lazy module’s service is the different instance.
Why the providedIn: 'any' exists. The providedIn: 'any' makes the service the new instance for each injector. The pattern is the per-injector, and the use is the rare.
The inject() function
The inject() function is the modern way to inject a dependency. It is the function call, and it can be used in the constructor, the field initializer, and the factory.
@Component({ selector: 'app-user', standalone: true, template: `` })
export class UserComponent {
private readonly userService = inject(UserService);
private readonly config = inject(APP_CONFIG);
}
The inject(UserService) is the field initializer, and the userService is the instance. The inject(APP_CONFIG) is the token, and the config is the value.
Why the inject() is the modern. The inject() is the Angular 14+ function, and it replaces the constructor parameter injection. The pattern is the field initializer, and the code is the concise.
Why the inject() can be used in the field initializer. The field initializer runs during the construction, and the injection context is the active. The inject() reads the context, and the instance is the resolved.
Why the inject() can be used in the factory. The useFactory can use the inject() instead of the deps.
{
provide: Logger,
useFactory: () => inject(AppConfig).debug ? new ConsoleLogger() : new NullLogger(),
}
The factory calls the inject(), and the dependency is the implicit. The pattern is the modern, and the deps array is the legacy.
Why the inject() requires the injection context. The inject() must be called during the construction, the field initializer, the factory, or the runInInjectionContext. The call outside the context is the error, and the runInInjectionContext is the escape.
Why the inject() is not the service locator. The inject() is the DI, and the call is the declaration. The service locator pattern is the anti-pattern, and the inject() is not the locator. The difference is the timing and the context.
Why the inject() is the preferred. The inject() is the modern, and the constructor injection is the legacy. The two are the equivalent, and the inject() is the concise.
Why the inject() can be the optional. The inject() accepts the options, and the { optional: true } makes the optional.
private readonly logger = inject(Logger, { optional: true });
The logger is the Logger | null, and the null is the absent. The pattern is the optional, and the @Optional decorator is the legacy.
The resolution modifiers
The resolution modifiers control where the resolution stops and what happens when the token is not found.
The @Self. The @Self stops the resolution at the current injector. The token must be provided at the current injector, or the error.
constructor(@Self() private service: UserService) {}
The @Self() is the current injector only. The pattern is the strict, and the service must be the local.
The @SkipSelf. The @SkipSelf skips the current injector and starts the resolution at the parent.
constructor(@SkipSelf() private service: UserService) {}
The @SkipSelf() is the parent’s, and the current injector is the skipped. The pattern is the inheritance, and the child can require the parent’s service.
The @Host. The @Host stops the resolution at the host component’s injector.
constructor(@Host() private service: UserService) {}
The @Host() is the host’s injector, and the resolution stops there. The pattern is the directive, and the directive requires the host’s service.
The @Optional. The @Optional makes the dependency optional. The resolution returns the null when the token is not found, instead of the error.
constructor(@Optional() private service: UserService | null) {}
The @Optional() is the optional, and the null is the absent. The pattern is the graceful, and the missing service is not the error.
Why the modifiers matter. The modifiers control the scope and the absence. The @Self, the @SkipSelf, and the @Host are the scope, and the @Optional is the absence. The four are the control, and the default is the walk up.
Why the modifiers can be the inject() options. The inject() accepts the options for the modifiers.
private readonly service = inject(UserService, { self: true });
private readonly service = inject(UserService, { skipSelf: true });
private readonly service = inject(UserService, { host: true });
private readonly service = inject(UserService, { optional: true });
The options are the modern, and the decorators are the legacy. The two are the equivalent, and the inject() is the preferred.
Why the modifiers are the advanced. The modifiers are for the cases where the default resolution is wrong. The default is the walk up, and the modifiers are the control. The use should be the deliberate.
The EnvironmentInjector and the runInInjectionContext
The EnvironmentInjector is the injector’s handle, and the runInInjectionContext is the way to run the code in the injector’s context.
@Component({ selector: 'app-user', standalone: true, template: `` })
export class UserComponent {
private readonly injector = inject(EnvironmentInjector);
ngOnInit(): void {
this.injector.runInInjectionContext(() => {
const service = inject(UserService);
});
}
}
The runInInjectionContext runs the callback in the injector’s context, and the inject() is the valid. The pattern is the escape for the code that needs the injection outside the construction.
Why the runInInjectionContext is the escape. The inject() requires the context, and the runInInjectionContext provides the context. The method is for the cases where the injection is needed in a callback or a method.
Why the EnvironmentInjector can be created. The createEnvironmentInjector creates the child injector, and the providers are the child’s.
const childInjector = createEnvironmentInjector(
[{ provide: Logger, useClass: FileLogger }],
parentInjector,
);
The child injector is the parent’s child, and the providers override the parent’s. The pattern is the dynamic, and the use is the advanced.
Why the runInInjectionContext is preferred over the service locator. The runInInjectionContext is the scoped, and the service locator is the global. The pattern is the modern, and the two are the different.
Why the EnvironmentInjector is the inject. The inject(EnvironmentInjector) is the current injector, and the runInInjectionContext is the context. The two are the pair, and the use is the advanced.
Why the DestroyRef is the related. The DestroyRef is the injectable that signals the destruction, and the toSignal and the takeUntilDestroyed use it. The DestroyRef is the cleanup, and the EnvironmentInjector is the resolution.
The testing and the DI
The DI is the testing’s foundation. The test provides the mocks at the test’s injector, and the component’s subtree uses them.
TestBed.configureTestingModule({
providers: [
{ provide: UserService, useValue: mockUserService },
],
});
The TestBed provides the mock, and the component’s inject(UserService) resolves to the mock. The pattern is the isolation, and the test is the deterministic.
Why the TestBed is the DI. The TestBed creates the injector, and the providers are the test’s. The component’s providers are the component’s, and the test’s override.
Why the useValue is the mock. The useValue provides the mock object, and the component uses it. The pattern is the simple, and the mock is the value.
Why the useClass is the test double. The useClass provides the test double class, and the component uses it. The pattern is the class, and the double is the instance.
Why the useFactory is the dynamic mock. The useFactory provides the dynamic, and the mock is the runtime. The pattern is the advanced, and the factory is the logic.
Why the override is the overrideProvider. The TestBed.overrideProvider overrides the provider after the setup, and the pattern is the advanced.
TestBed.overrideProvider(UserService, { useValue: mockUserService });
The overrideProvider is the method, and the mock is the value. The pattern is the override, and the setup is the order.
Why the runInInjectionContext is the test helper. The TestBed.runInInjectionContext runs the callback in the test’s context, and the inject() is the valid. The pattern is the test, and the context is the setup.
Why the inject() is the modern in the test. The inject() is the modern, and the TestBed.inject is the legacy. The two are the equivalent, and the inject() is the concise.
Complete Example Session
import {
Component, Injectable, InjectionToken, inject, EnvironmentInjector,
runInInjectionContext, Self, SkipSelf, Host, Optional,
} from '@angular/core';
// ============================================
// PART 1: THE CLASS TOKEN
// ============================================
@Injectable({ providedIn: 'root' })
export class UserService {
getUsers(): User[] { return []; }
}
@Component({ selector: 'app-user', standalone: true, template: `` })
export class UserComponent {
private readonly userService = inject(UserService);
}
// ============================================
// PART 2: THE INJECTION TOKEN
// ============================================
export const API_URL = new InjectionToken<string>('API_URL');
export const appConfig = {
providers: [
{ provide: API_URL, useValue: 'https://api.example.com' },
],
};
@Component({ selector: 'app-api', standalone: true, template: `` })
export class ApiComponent {
private readonly apiUrl = inject(API_URL);
}
// ============================================
// PART 3: THE USE CLASS
// ============================================
interface Logger {
log(message: string): void;
}
class ConsoleLogger implements Logger {
log(message: string): void { console.log(message); }
}
export const loggerProviders = [
{ provide: Logger, useClass: ConsoleLogger },
];
// ============================================
// PART 4: THE USE FACTORY
// ============================================
export const loggerFactory = {
provide: Logger,
useFactory: () => {
const config = inject(APP_CONFIG);
return config.debug ? new ConsoleLogger() : new NullLogger();
},
};
// ============================================
// PART 5: THE USE EXISTING
// ============================================
export const aliasProviders = [
{ provide: Logger, useClass: ConsoleLogger },
{ provide: OldLogger, useExisting: Logger },
];
// ============================================
// PART 6: THE RESOLUTION MODIFIERS
// ============================================
@Component({ selector: 'app-modifier', standalone: true, template: `` })
export class ModifierComponent {
private readonly self = inject(UserService, { self: true });
private readonly skipSelf = inject(UserService, { skipSelf: true });
private readonly host = inject(UserService, { host: true });
private readonly optional = inject(Logger, { optional: true });
}
// ============================================
// PART 7: THE RUN IN INJECTION CONTEXT
// ============================================
@Component({ selector: 'app-context', standalone: true, template: `` })
export class ContextComponent {
private readonly injector = inject(EnvironmentInjector);
ngOnInit(): void {
this.injector.runInInjectionContext(() => {
const service = inject(UserService);
});
}
}
// ============================================
// PART 8: THE PROVIDED IN
// ============================================
@Injectable({ providedIn: 'root' })
export class RootService {}
@Injectable({ providedIn: 'any' })
export class AnyService {}
@Injectable({ providedIn: SomeModule })
export class ModuleService {}
// ============================================
// PART 9: THE TEST
// ============================================
TestBed.configureTestingModule({
providers: [
{ provide: UserService, useValue: mockUserService },
],
});
// ============================================
// PART 10: WHAT NOT TO DO
// ============================================
// Don't use a string token in new code
// { provide: 'API_URL', useValue: '...' } // use InjectionToken
// Don't create the injector inside the code
// const injector = createEnvironmentInjector(...); // use the inject()
// Don't use the service locator pattern
// const service = injector.get(UserService); // the inject() is the modern
// Don't forget the deps in the factory (or use inject)
// { useFactory: (config) => ... } // the config is undefined
// Don't use the @Self without the local provider
// @Self() service: UserService // the error if the not provided
// Don't mix the constructor and the inject() in the same class
// constructor(private a: A) { const b = inject(B); } // the style
The ten parts cover the class token, the injection token, the use class, the use factory, the use existing, the resolution modifiers, the run in injection context, the provided in, the test, and the anti-patterns.
Quick Reference
The Provider Types
| Type | Purpose |
|---|---|
useClass | The class instance |
useValue | The value |
useFactory | The factory function |
useExisting | The alias |
The Injection Tokens
| Token | Purpose |
|---|---|
| The class | The service |
InjectionToken<T> | The non-class |
| The string | The legacy |
The providedIn
| Value | Scope |
|---|---|
'root' | The singleton, tree-shakable |
'any' | The per-injector |
SomeModule | The module’s scope |
'platform' | The platform |
The Resolution Modifiers
| Modifier | Purpose |
|---|---|
@Self | The current injector only |
@SkipSelf | The parent, skipping the current |
@Host | The host component’s injector |
@Optional | The null when absent |
The inject() Options
| Option | Purpose |
|---|---|
{ self: true } | The @Self |
{ skipSelf: true } | The @SkipSelf |
{ host: true } | The @Host |
{ optional: true } | The @Optional |
The Testing
| Pattern | Purpose |
|---|---|
useValue | The mock object |
useClass | The test double |
useFactory | The dynamic mock |
overrideProvider | The override |
Best Practices
✅ Do This:
// Use the providedIn: 'root' for the singleton
@Injectable({ providedIn: 'root' })
export class UserService {} // ✅
// Use the InjectionToken for the non-class
export const API_URL = new InjectionToken<string>('API_URL'); // ✅
// Use the inject() function
private readonly service = inject(UserService); // ✅
// Use the useExisting for the alias
{ provide: OldLogger, useExisting: Logger } // ✅
// Use the useFactory with the inject()
{ provide: Logger, useFactory: () => inject(Config).debug } // ✅
// Use the runInInjectionContext for the callback
this.injector.runInInjectionContext(() => inject(Service)); // ✅
// Use the useValue for the mock
{ provide: UserService, useValue: mockUserService } // ✅
❌ Don’t Do This:
// Don't use a string token in new code
{ provide: 'API_URL', useValue: '...' } // ⚠️
// Don't create the injector inside the code
const injector = createEnvironmentInjector(...); // ⚠️
// Don't use the service locator pattern
const service = injector.get(UserService); // ⚠️
// Don't forget the deps in the factory
{ useFactory: (config) => ... } // the config is undefined // ⚠️
// Don't use the @Self without the local provider
@Self() service: UserService // the error if not provided // ⚠️
// Don't mix the constructor and the inject()
constructor(private a: A) { const b = inject(B); } // ⚠️
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| The string token | The collision | Use InjectionToken |
The missing deps | The undefined | Use inject() |
The @Self without the provider | The error | Provide it |
| The service locator | The anti-pattern | Use inject() |
| The scope confusion | The wrong instance | Check the providedIn |
The useClass for the alias | The new instance | Use useExisting |
| The injector outside the context | The error | Use runInInjectionContext |
Real-World Examples
1. The class token
@Injectable({ providedIn: 'root' })
export class UserService {}
2. The InjectionToken
export const API_URL = new InjectionToken<string>('API_URL');
3. The useClass
{ provide: Logger, useClass: ConsoleLogger }
4. The useValue
{ provide: API_URL, useValue: 'https://api.example.com' }
5. The useFactory
{ provide: Logger, useFactory: () => inject(Config).debug ? new ConsoleLogger() : new NullLogger() }
6. The useExisting
{ provide: OldLogger, useExisting: Logger }
7. The @Optional
private readonly logger = inject(Logger, { optional: true });
8. The @Self
private readonly service = inject(UserService, { self: true });
9. The runInInjectionContext
this.injector.runInInjectionContext(() => inject(UserService));
10. The test
TestBed.configureTestingModule({
providers: [{ provide: UserService, useValue: mockUserService }],
});
Visual: The Injector Hierarchy
┌──────────────────────────────────────────────────────────┐
│ EnvironmentInjector (root) │
│ The application-wide services │
│ providedIn: 'root', the app config's providers │
│ │ │
│ ├── EnvironmentInjector (lazy module) │
│ │ The module's services │
│ │ │
│ └── ElementInjector (component) │
│ The component's providers │
│ │ │
│ └── ElementInjector (child component) │
│ The child's providers │
│ │
│ The resolution: the element first, then the environment.│
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Provider Types
┌──────────────────────────────────────────────────────────┐
│ useClass: ConsoleLogger │
│ The injector creates the instance. │
│ │
│ useValue: 'https://api.example.com' │
│ The injector returns the value. │
│ │
│ useFactory: () => inject(Config).debug ? ... : ... │
│ The injector calls the factory. │
│ │
│ useExisting: Logger │
│ The injector returns the existing instance. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The Resolution Modifiers
┌──────────────────────────────────────────────────────────┐
│ THE TREE │
│ │
│ RootInjector │
│ └── ParentInjector │
│ └── ChildInjector │
│ │
│ @Self() → the current injector only │
│ @SkipSelf() → the parent, skipping the current │
│ @Host() → the host component's injector │
│ @Optional() → the null when absent │
│ │
│ The default: the walk up the hierarchy. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The providedIn
┌──────────────────────────────────────────────────────────┐
│ providedIn: 'root' │
│ The singleton for the application. │
│ The tree-shakable. │
│ │
│ providedIn: 'any' │
│ The new instance per injector. │
│ The not tree-shakable. │
│ │
│ providedIn: SomeModule │
│ The module's scope. │
│ The different instance per the module. │
│ │
│ providedIn: 'platform' │
│ The platform's scope. │
│ The shared across the applications. │
│ │
└──────────────────────────────────────────────────────────┘
Visual: The inject() Context
┌──────────────────────────────────────────────────────────┐
│ THE VALID CONTEXTS │
│ │
│ The constructor │
│ constructor() { const s = inject(Service); } │
│ │
│ The field initializer │
│ private readonly s = inject(Service); │
│ │
│ The factory │
│ useFactory: () => inject(Service) │
│ │
│ The runInInjectionContext │
│ injector.runInInjectionContext(() => inject(Service));│
│ │
│ THE INVALID CONTEXT │
│ │
│ The method │
│ ngOnInit() { const s = inject(Service); } // ❌ │
│ │
│ The callback │
│ setTimeout(() => inject(Service), 0); // ❌ │
│ │
└──────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| EnvironmentInjector | The module-level |
| ElementInjector | The component-level |
| The resolution | The element first, then the environment |
useClass | The class instance |
useValue | The value |
useFactory | The factory |
useExisting | The alias |
InjectionToken | The typed token |
providedIn: 'root' | The tree-shakable singleton |
@Self | The current only |
@SkipSelf | The parent |
@Host | The host |
@Optional | The null when absent |
Key takeaways:
- Angular has two injector hierarchies: the EnvironmentInjector and the ElementInjector — the environment is the module-level, the element is the component-level, and the resolution is the element first
- The provider types determine what the injector returns — the
useClasscreates the instance, theuseValuereturns the value, theuseFactorycalls the factory, and theuseExistingreturns the alias - The
InjectionTokenis the typed token for the non-class dependencies — theInjectionToken<T>is the generic, and the string token is the legacy - The
providedIn: 'root'makes the provider tree-shakable — the singleton for the application, and the bundler can remove the unused services - The
inject()function is the modern way to inject — it can be used in the constructor, the field initializer, the factory, and therunInInjectionContext - The resolution modifiers control the scope and the absence — the
@Self, the@SkipSelf, the@Host, and the@Optionalare the four, and theinject()options are the modern - The
runInInjectionContextis the escape — it runs the callback in the injector’s context, and theinject()is the valid - The
EnvironmentInjectoris the injectable handle — thecreateEnvironmentInjectorcreates the child, and therunInInjectionContextis the context - The DI is the testing’s foundation — the
TestBedprovides the mocks, and theuseValue, theuseClass, and theuseFactoryare the patterns - The injector hierarchy is the DI’s power — the root services are the application-wide, the module services are the feature-scoped, and the component services are the local
Remember: Angular’s DI is the hierarchical injector with the two hierarchies, the provider types, the injection tokens, and the resolution modifiers. The inject() is the modern, the InjectionToken is the typed, and the providedIn is the scope. The resolution modifiers control the scope, and the runInInjectionContext is the escape. The DI is the backbone, and the patterns are the vocabulary.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!