Angular 21 🅰️ The inject() Function
For most of Angular’s history, dependencies came in through the constructor — constructor(private userService: UserService). It works, it’s explicit, and it’s still supported. But it has friction: subclasses must forward constructor parameters, factory functions can’t use it, and signal-based code doesn’t fit naturally. The inject() function, introduced in Angular 14, changes the model. Instead of declaring dependencies as constructor parameters, you call inject(Service) anywhere in an injection context — the constructor, a field initializer, a factory function, a guard, or a service method. It returns the instance and participates in Angular’s dependency injection the same way. This chapter is about inject() — what it is, where it works, when to use it over the constructor, and the patterns that make it valuable.
Key point: inject() reads a dependency from the current injector. It only works in an injection context — a place where Angular knows which injector to look in. That context exists in constructors, field initializers, factory functions, route guards, and a few other specific places. Call inject() outside a context and it throws. Inside a context, it’s the modern way to get a service — shorter than constructor injection, works in more places, and composes with signals.
What inject() is
inject() is a function that returns an instance from the current injector.
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
standalone: true,
template: `...`
})
export class UserListComponent {
private userService = inject(UserService);
}
The component declares a field and assigns it the result of inject(UserService). Angular provides the instance when the component is created.
What inject() does:
- Reads a dependency from the current injector
- Uses the injection context to know which injector
- Returns the instance
- Registers the dependency for lifecycle management
What inject() doesn’t do:
- Doesn’t work outside an injection context
- Doesn’t change scope — it reads from whatever injector the context provides
- Doesn’t replace
providers— services still need to be provided
The comparison with constructor injection:
// Constructor injection
export class UserListComponent {
constructor(private userService: UserService) {}
}
// inject()
export class UserListComponent {
private userService = inject(UserService);
}
Both get the same instance. The inject() version has less boilerplate, works in more places, and composes with signals.
Why inject() exists: The constructor has limits. Subclasses must forward constructor parameters. Factory functions and standalone functions can’t use constructor. Signals want dependencies available as field initializers, not in the constructor body. inject() solves all of these by decoupling “get a dependency” from “the constructor.”
Why “inject” is the right name: It’s the verb — you’re injecting a dependency into the current context. The function reads from the injector. “Inject” describes both the action and the mechanism. Constructor parameters do the same thing but the name is implicit in the position.
Injection context
inject() only works in an injection context — a place where Angular knows which injector to use.
Where injection context exists:
| Location | Works |
|---|---|
| Constructor | ✅ |
| Field initializer | ✅ |
| Factory function | ✅ |
| Route guard function | ✅ |
| Resolver function | ✅ |
provideX factory | ✅ |
ngOnInit | ❌ |
ngAfterViewInit | ❌ |
| Event handler | ❌ |
setTimeout callback | ❌ |
Promise.then | ❌ |
After await | ❌ |
The rule: Injection context exists during component/service construction. The constructor runs in context; field initializers run as part of construction; factory functions run when Angular calls them to create a value. Everything else runs later.
Field initializers are in context:
export class UserListComponent {
private userService = inject(UserService); // ✅
}
Field initializers run as part of instantiation — Angular is still in the construction phase.
Methods are not in context:
export class UserListComponent {
private userService = inject(UserService);
ngOnInit(): void {
// const another = inject(OtherService); // ❌ not in context
}
}
ngOnInit runs after construction. If you need a service there, store it in a field first.
The fix for methods:
export class UserListComponent {
private userService = inject(UserService); // ✅ field
private otherService = inject(OtherService); // ✅ field
ngOnInit(): void {
this.userService.load(); // ✅ use stored fields
this.otherService.init();
}
}
Capture dependencies as fields; use them in methods.
Outside a context:
// This throws at runtime
const service = inject(UserService);
Angular can’t find an injector. The error message tells you inject() must be called in an injection context.
Why the restriction: inject() needs to know which injector to query. In a constructor or field initializer, that’s the component’s injector. In a factory, it’s the injector where the factory is provided. Outside those, there’s no unambiguous injector — Angular can’t guess.
How to enter an injection context: runInInjectionContext().
import { runInInjectionContext, EnvironmentInjector, inject } from '@angular/core';
function setup(injector: EnvironmentInjector) {
runInInjectionContext(injector, () => {
const service = inject(UserService); // ✅ now in context
});
}
Rare, but useful when you need inject() in a place Angular doesn’t provide context.
Why field initializers work: Angular creates the class in a specific phase. During construction, it establishes the injection context. Field initializers run during that phase. Once construction completes, the context is gone. That’s why fields can call
inject()and methods can’t.
inject() in fields vs constructor
Both work. Which to prefer?
Constructor injection:
export class UserListComponent {
users: User[] = [];
constructor(private userService: UserService) {
this.users = userService.getUsers();
}
}
The parameter is declared and assigned by the constructor. The body can use the service immediately.
inject() in fields:
export class UserListComponent {
private userService = inject(UserService);
users = this.userService.getUsers();
}
The field is assigned via inject(). Other fields can reference it.
Comparison:
| Aspect | Constructor | inject() |
|---|---|---|
| Boilerplate | constructor(private x: T) | private x = inject(T) |
| Subclass forwarding | Required | Not needed |
| Works in factory | ❌ | ✅ |
| Composes with signals | ⚠️ | ✅ |
| Multiple deps | Long signature | One line each |
| Initialization order | Constructor runs first | Fields run in order |
Order matters for field initializers:
export class Component {
private a = inject(AService);
private b = inject(BService);
private value = this.a.getValue(); // ✅ a is set
}
Fields initialize in declaration order. a is available when value is computed.
Constructor ordering:
export class Component {
constructor(
private a: AService,
private b: BService
) {
// Both available here
}
}
Both parameters are available in the constructor body.
Why inject() is preferred for new code:
- Less boilerplate
- Works in more places (factory functions, guards, resolvers)
- No subclass forwarding
- Composes with signals and field initializers
- Consistent with the modern Angular style
When to use constructor: Rarely in new code. The main reasons are:
- Inheriting from a class with a constructor
- Explicit ordering of dependency construction
- Style preference
Why not both: Mixing inject() and constructor injection in the same class works but is confusing. Pick one style per class.
Why field initializers beat the constructor for most cases: They’re evaluated in declaration order, they can reference each other, and they avoid the “parameters only” restriction of the constructor.
private userService = inject(UserService)reads naturally — the field is the service. No separate parameter to name.
inject() in services
Services use inject() the same way.
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LoggerService } from './logger.service';
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private logger = inject(LoggerService);
getUsers(): Promise<User[]> {
this.logger.log('Fetching users');
return this.http.get<User[]>('/api/users').toPromise();
}
}
Both dependencies are injected as fields. The service works without a constructor.
Why this is cleaner: A service with three dependencies in the constructor has a long signature. With inject(), each is a short field. Adding or removing a dependency is a one-line change.
Services in different scopes:
@Injectable({ providedIn: 'root' })
export class GlobalService {
private http = inject(HttpClient);
}
@Injectable() // provided at component level
export class LocalService {
private global = inject(GlobalService); // gets the root instance
}
inject() respects the hierarchical injector — LocalService gets whatever GlobalService resolves to based on scope.
Service injecting itself (for @SkipSelf patterns):
@Injectable({ providedIn: 'root' })
export class TreeService {
private parent = inject(TreeService, { skipSelf: true, optional: true });
}
The options — optional, self, skipSelf, host — map to the decorators covered in the previous chapter.
Why services benefit most: They often have multiple dependencies, no other constructor logic, and don’t need explicit parameter ordering. inject() fields are the natural fit.
Why the field-per-dependency pattern: Each dependency is a named field. It’s visible at the top of the class, easy to scan, and self-documenting. The constructor with multiple parameters pushes the same information into a compact signature that’s harder to read. Fields win for clarity.
inject() in factory functions
Factory functions run in an injection context. They can call inject().
import { InjectionToken, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
export const API_URL = new InjectionToken<string>('API_URL');
export function createApiClient(): ApiClient {
const http = inject(HttpClient);
const url = inject(API_URL);
return new ApiClient(http, url);
}
When Angular calls this factory, it provides an injection context. The factory reads both dependencies and constructs the client.
Providing via the factory:
export const API_CLIENT = new InjectionToken<ApiClient>('API_CLIENT');
providers: [
{ provide: API_URL, useValue: '/api' },
{
provide: API_CLIENT,
useFactory: createApiClient
}
]
When API_CLIENT is injected, Angular calls createApiClient in a context where inject() works.
Factory with dependencies declared in deps: The older form.
{
provide: API_CLIENT,
useFactory: (http: HttpClient, url: string) => new ApiClient(http, url),
deps: [HttpClient, API_URL]
}
deps lists what to pass. Works but verbose — inject() in the factory body replaces deps.
New form with inject():
{
provide: API_CLIENT,
useFactory: () => {
const http = inject(HttpClient);
const url = inject(API_URL);
return new ApiClient(http, url);
}
}
The factory calls inject() directly. No deps array needed.
Why this matters: Factories often need to compute something from dependencies. inject() in the factory body is direct — no signature, no separate array. It’s the modern way to write useFactory.
Why factory context exists: Angular calls factories to create values. It controls when and where, so it can establish a context. That’s why
inject()works there and not in arbitrary functions. The factory contract — “Angular calls this to provide a value” — implies a context.
inject() in guards and resolvers
Standalone route guards and resolvers are functions. They can call inject().
Functional guard:
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true;
}
return router.createUrlTree(['/login']);
};
The guard is a function, not a class. inject() gives it access to services.
Functional resolver:
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
export const userResolver: ResolveFn<User> = (route) => {
const userService = inject(UserService);
const id = Number(route.paramMap.get('id'));
return userService.getUser(id);
};
Same pattern — a function that returns data, using inject() for dependencies.
Using in routes:
export const routes: Routes = [
{
path: 'profile',
component: ProfileComponent,
canActivate: [authGuard],
resolve: { user: userResolver }
}
];
Both are just functions in the route config.
Why functional guards/resolvers are better: The class-based versions needed @Injectable and were harder to test. The functional versions are plain functions — pass them, mock them, test them directly. inject() in the body provides dependencies without a constructor.
Why
inject()in guards works: Angular calls the guard function during routing. It establishes an injection context so the guard can access services. The same mechanism as factories — Angular controls when the function runs and provides a context.
Common patterns with inject()
Several patterns show up repeatedly.
Inject + signals:
@Component({ /* ... */ })
export class UserListComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
ngOnInit(): void {
this.userService.getUsers().subscribe(users => {
this.users.set(users);
});
}
}
The service is a field; the signal is a field. Together they form the component’s state.
Inject + computed:
export class UserListComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
count = computed(() => this.users().length);
}
computed depends on the signal, not the service — but the pattern composes.
Inject + effect:
export class UserListComponent {
private userService = inject(UserService);
userId = input.required<number>();
constructor() {
effect(() => {
this.userService.getUser(this.userId()).subscribe(user => {
// update state
});
});
}
}
The effect runs when userId changes; the service is available for the call.
Inject in a mixin:
type Constructor<T = {}> = new (...args: any[]) => T;
function WithLogger<T extends Constructor>(Base: T) {
return class extends Base {
private logger = inject(LoggerService);
};
}
Mixins can use inject() because the field initializer runs in context.
Inject with options:
export class Component {
private optional = inject(OptionalService, { optional: true });
private parent = inject(TreeService, { skipSelf: true });
private own = inject(OwnService, { self: true });
private host = inject(HostService, { host: true });
}
The options match the parameter decorators — @Optional, @SkipSelf, @Self, @Host.
Why these patterns matter: inject() fits signal-based code naturally. Fields are where signals live; fields are where inject() goes. The two go together. That’s part of why inject() replaced constructor injection as the default style.
Why
inject()and signals pair well: Both are field-based. A component’s state is a set of fields — signals for state, injected services for dependencies. Constructor injection puts dependencies in the constructor, forcing a split.inject()unifies everything as fields.
Testing with inject()
inject() works with TestBed in tests.
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
TestBed.inject(Service) uses the same injection context — the test injector.
Providing mocks:
TestBed.configureTestingModule({
providers: [
{ provide: UserService, useValue: mockUserService }
]
});
const service = TestBed.inject(UserService);
// Returns the mock
The mock replaces the real service.
Component testing:
TestBed.configureTestingModule({
imports: [UserListComponent]
});
const fixture = TestBed.createComponent(UserListComponent);
The component’s inject() calls resolve against the test injector.
Why this works: TestBed provides an injector. When the component or service is created, inject() reads from it. The pattern is identical to production — the injector is different.
Why inject() is more testable than constructor injection: Not by itself, but the modern patterns — functional guards, standalone services, field-based components — are easier to test because they don’t need class instantiation boilerplate. TestBed.inject() and TestBed.createComponent() handle everything.
Why test injectors matter: Tests need to control dependencies.
TestBedgives a controlled injector where you decide what’s provided.inject()reads from it. That’s how mocking works — replace the provider, and everyinject()call returns the mock.
A full example
A component using inject() in several ways.
// ============================================
// SERVICES
// ============================================
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
export interface User {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class LoggerService {
log(msg: string): void {
console.log(`[LOG] ${msg}`);
}
}
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private logger = inject(LoggerService);
async getUsers(): Promise<User[]> {
this.logger.log('Fetching users');
return this.http.get<User[]>('/api/users').toPromise() as Promise<User[]>;
}
async getUser(id: number): Promise<User> {
this.logger.log(`Fetching user ${id}`);
return this.http.get<User>(`/api/users/${id}`).toPromise() as Promise<User>;
}
}
// ============================================
// COMPONENT
// ============================================
import { Component, signal, computed, inject, input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-user-list',
standalone: true,
imports: [CommonModule],
template: `
<h2>Users ({{ count() }})</h2>
<ul>
@for (user of users(); track user.id) {
<li>{{ user.name }} — {{ user.email }}</li>
}
</ul>
`
})
export class UserListComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
count = computed(() => this.users().length);
async ngOnInit(): Promise<void> {
const users = await this.userService.getUsers();
this.users.set(users);
}
}
// ============================================
// FUNCTIONAL GUARD
// ============================================
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
if (auth.isLoggedIn()) {
return true;
}
return router.createUrlTree(['/login']);
};
// ============================================
// FUNCTIONAL RESOLVER
// ============================================
import { ResolveFn } from '@angular/router';
export const userResolver: ResolveFn<User> = (route) => {
const userService = inject(UserService);
const id = Number(route.paramMap.get('id'));
return userService.getUser(id);
};
// ============================================
// ROUTES
// ============================================
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'users',
component: UserListComponent,
canActivate: [authGuard]
},
{
path: 'users/:id',
resolve: { user: userResolver },
loadComponent: () => import('./user-detail.component').then(c => c.UserDetailComponent)
}
];
What this shows:
UserServiceusesinject()forHttpClientandLoggerServiceUserListComponentinjectsUserServiceand uses signalsauthGuardis a functional guard usinginject()userResolveris a functional resolver usinginject()- Routes reference the functions
Every dependency goes through inject(). No constructors anywhere.
Why this shape: It’s the modern Angular pattern. Services, components, guards, and resolvers all use
inject(). Signals and functional APIs. The result is less boilerplate, clearer dependencies, and a consistent style across the codebase.
Complete Example Session
# ============================================
# PART 1: BASIC INJECT
# ============================================
cat > user.service.ts << 'EOF'
import { Injectable } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class UserService {
getUsers(): string[] {
return ['Alice', 'Bob'];
}
}
EOF
cat > user-list.component.ts << 'EOF'
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-user-list',
standalone: true,
template: `<p>{{ users.length }} users</p>`
})
export class UserListComponent {
private userService = inject(UserService);
users = this.userService.getUsers();
}
EOF
npx tsc --noEmit user-list.component.ts
# (no errors)
# ============================================
# PART 2: INJECT IN A SERVICE
# ============================================
cat > logger.service.ts << 'EOF'
import { Injectable, inject } from '@angular/core';
import { UserService } from './user.service';
@Injectable({ providedIn: 'root' })
export class ReportService {
private userService = inject(UserService);
report(): string {
return `Total users: ${this.userService.getUsers().length}`;
}
}
EOF
npx tsc --noEmit logger.service.ts
# (no errors)
# ============================================
# PART 3: FIELD INITIALIZER ORDER
# ============================================
cat > order.ts << 'EOF'
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-ordered',
standalone: true,
template: `<p>{{ count }}</p>`
})
export class OrderedComponent {
private userService = inject(UserService);
users = this.userService.getUsers();
count = this.users.length;
}
EOF
npx tsc --noEmit order.ts
# (no errors)
# ============================================
# PART 4: INJECT WITH OPTIONS
# ============================================
cat > options.ts << 'EOF'
import { Component, inject, InjectionToken } from '@angular/core';
const OPTIONAL = new InjectionToken<string>('OPTIONAL');
@Component({
selector: 'app-options',
standalone: true,
template: `<p>{{ value }}</p>`
})
export class OptionsComponent {
private optional = inject(OPTIONAL, { optional: true });
value = this.optional ?? 'default';
}
EOF
npx tsc --noEmit options.ts
# (no errors)
# ============================================
# PART 5: FUNCTIONAL GUARD
# ============================================
cat > auth.guard.ts << 'EOF'
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
export const authGuard: CanActivateFn = () => {
const router = inject(Router);
const isLoggedIn = true;
if (isLoggedIn) return true;
return router.createUrlTree(['/login']);
};
EOF
npx tsc --noEmit auth.guard.ts
# (no errors)
# ============================================
# PART 6: FUNCTIONAL RESOLVER
# ============================================
cat > user.resolver.ts << 'EOF'
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { UserService } from './user.service';
export const usersResolver: ResolveFn<string[]> = () => {
return inject(UserService).getUsers();
};
EOF
npx tsc --noEmit user.resolver.ts
# (no errors)
# ============================================
# PART 7: FACTORY WITH INJECT
# ============================================
cat > factory.ts << 'EOF'
import { InjectionToken, inject } from '@angular/core';
import { UserService } from './user.service';
export interface Report {
total: number;
}
export const REPORT = new InjectionToken<Report>('REPORT');
export function createReport(): Report {
const users = inject(UserService).getUsers();
return { total: users.length };
}
export const providers = [
{ provide: REPORT, useFactory: createReport }
];
EOF
npx tsc --noEmit factory.ts
# (no errors)
# ============================================
# PART 8: INJECTION CONTEXT
# ============================================
cat > context.ts << 'EOF'
import { Component, inject, Injector, runInInjectionContext } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-context',
standalone: true,
template: `<p>{{ message }}</p>`
})
export class ContextComponent {
private injector = inject(Injector);
message = '';
ngOnInit(): void {
// Outside context — must wrap
runInInjectionContext(this.injector, () => {
const userService = inject(UserService);
this.message = `${userService.getUsers().length} users`;
});
}
}
EOF
npx tsc --noEmit context.ts
# (no errors)
# ============================================
# PART 9: ERRORS
# ============================================
cat > error.ts << 'EOF'
import { Component, inject } from '@angular/core';
import { UserService } from './user.service';
@Component({
selector: 'app-bad',
standalone: true,
template: ``
})
export class BadComponent {
ngOnInit(): void {
const service = inject(UserService); // ❌ not in context
}
}
EOF
npx tsc --noEmit error.ts
# (no errors — type checks, fails at runtime)
rm error.ts
Quick Reference
Basic Syntax
| Form | Meaning |
|---|---|
inject(Service) | Get the service instance |
inject(TOKEN) | Get the value for a token |
private x = inject(Service) | Field assignment |
const x = inject(Service) | Local variable |
Injection Contexts
| Location | Works |
|---|---|
| Constructor | ✅ |
| Field initializer | ✅ |
| Factory function | ✅ |
| Route guard | ✅ |
| Route resolver | ✅ |
provideX factory | ✅ |
ngOnInit | ❌ |
| Methods | ❌ |
setTimeout | ❌ |
Promise.then | ❌ |
After await | ❌ |
Options
| Option | Equivalent |
|---|---|
{ optional: true } | @Optional |
{ self: true } | @Self |
{ skipSelf: true } | @SkipSelf |
{ host: true } | @Host |
Constructor vs inject()
| Aspect | Constructor | inject() |
|---|---|---|
| Boilerplate | More | Less |
| Works in factory | ❌ | ✅ |
| Works in guards | ❌ | ✅ |
| Subclass forwarding | Required | Not needed |
| Field ordering | N/A | Declaration order |
| Signals fit | ⚠️ | ✅ |
| Recommended | Legacy | Modern |
inject() in Factories
| Form | Deps needed |
|---|---|
useFactory: () => { const s = inject(S); ... } | ❌ |
useFactory: (s) => ..., deps: [S] | ✅ |
inject() in Guards and Resolvers
| API | Type |
|---|---|
CanActivateFn | (route, state) => boolean | UrlTree |
CanDeactivateFn | (component, ...) => boolean | UrlTree |
ResolveFn<T> | (route, state) => T | Observable<T> |
All use inject() | In the function body |
Injection Context Helpers
| Helper | Purpose |
|---|---|
runInInjectionContext(injector, fn) | Run code in context |
assertInInjectionContext(fn) | Assert context exists |
Injector | Get the injector itself |
Field Initialization Order
export class Component {
private a = inject(AService); // first
private b = inject(BService); // second
private value = this.a.get(); // third — a is available
}
Fields initialize in declaration order.
Common Patterns
| Pattern | Code |
|---|---|
| Single dep | private s = inject(Service) |
| Optional | inject(Service, { optional: true }) |
| Parent | inject(Service, { skipSelf: true }) |
| Own | inject(Service, { self: true }) |
| Host | inject(Service, { host: true }) |
| Token | inject(TOKEN) |
| Injector | inject(Injector) |
Error Messages
| Error | Cause |
|---|---|
inject() must be called from an injection context | Called outside context |
No provider for X | Service not provided |
NullInjectorError | Missing provider, not optional |
Migration from Constructor
| Before | After |
|---|---|
constructor(private s: S) {} | private s = inject(S) |
constructor(@Optional() s: S) {} | inject(S, { optional: true }) |
constructor(@SkipSelf() s: S) {} | inject(S, { skipSelf: true }) |
constructor(@Inject(T) x: V) {} | inject(T) |
Testing
| Step | Code |
|---|---|
| Configure | TestBed.configureTestingModule({ providers: [...] }) |
| Inject service | TestBed.inject(Service) |
| Create component | TestBed.createComponent(Component) |
Best Practices
✅ Do This:
// Use inject() for new code
private userService = inject(UserService); // ✅
// Use fields, not constructor parameters
private http = inject(HttpClient);
private logger = inject(LoggerService); // ✅
// Use inject() in services
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
} // ✅
// Use inject() in functional guards
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
return auth.isLoggedIn();
}; // ✅
// Use inject() in functional resolvers
export const userResolver: ResolveFn<User> = (route) => {
return inject(UserService).getUser(Number(route.params['id']));
}; // ✅
// Use inject() in factory functions
{
provide: CLIENT,
useFactory: () => new Client(inject(HttpClient))
} // ✅
// Use options for optional or scoped deps
private parent = inject(Service, { skipSelf: true }); // ✅
// Declare fields in dependency order
private logger = inject(LoggerService);
private service = inject(UserService); // ✅
// Use runInInjectionContext when needed
runInInjectionContext(this.injector, () => {
const s = inject(Service);
}); // ✅
❌ Don’t Do This:
// Don't use inject() in methods
ngOnInit(): void {
const s = inject(Service); // ❌ not in context // ❌
}
// Don't use inject() after await
async load(): Promise<void> {
await something();
const s = inject(Service); // ❌ // ❌
}
// Don't use inject() in event handlers
onClick(): void {
const s = inject(Service); // ❌ // ❌
}
// Don't mix constructor and inject() in the same class
constructor(private a: A) {}
private b = inject(B); // ⚠️ pick one // ⚠️
// Don't rely on undefined order
private a = inject(A);
private b = this.a.getValue(); // ⚠️ depends on order // ⚠️
// Don't skip options for optional services
private s = inject(Service); // throws if not provided // ⚠️
// Don't forget to provide the service
// inject(Service) fails if no provider // ❌
// Don't ignore the injection context error
const s = inject(Service); // in a plain function // ❌
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
inject() in method | Not in context | Use field |
After await | Context lost | Capture before await |
| Optional without option | Throws if missing | Add { optional: true } |
| Mixing styles | Confusing | Pick one |
| Field order dependencies | Value used before set | Order fields correctly |
| In factory without context | Runtime error | Use useFactory in context |
| In event handler | Not in context | Store as field |
| Forgot provider | No provider error | Add to providers |
| Subclass constructor | Inherited params | Use inject() in fields |
| Testing without TestBed | Real injector missing | Use TestBed |
Real-World Examples
1. Basic inject
private userService = inject(UserService);
2. Multiple injections
private http = inject(HttpClient);
private logger = inject(LoggerService);
private config = inject(APP_CONFIG);
3. Optional injection
private logger = inject(LoggerService, { optional: true });
4. SkipSelf
private parent = inject(TreeService, { skipSelf: true });
5. Self
private own = inject(StateService, { self: true });
6. Host
private host = inject(HostContextService, { host: true });
7. Injection token
private apiUrl = inject(API_URL);
8. Injector
private injector = inject(Injector);
9. In a service
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
}
10. In a functional guard
export const guard: CanActivateFn = () => {
return inject(AuthService).isLoggedIn();
};
11. In a resolver
export const resolver: ResolveFn<User> = (route) => {
return inject(UserService).getUser(route.params['id']);
};
12. In a factory
{
provide: CLIENT,
useFactory: () => new Client(inject(HttpClient))
}
13. In a provideX function
export function provideClient(): Provider {
return {
provide: CLIENT,
useFactory: () => new Client(inject(HttpClient))
};
}
14. Field with signal
private userService = inject(UserService);
users = signal<User[]>([]);
15. Field with computed
private userService = inject(UserService);
count = computed(() => this.userService.count());
16. In a mixin
function WithLogging<T extends Constructor>(Base: T) {
return class extends Base {
private logger = inject(LoggerService);
};
}
17. With runInInjectionContext
runInInjectionContext(injector, () => {
const service = inject(Service);
});
18. In a base class
abstract class BaseComponent {
protected logger = inject(LoggerService);
}
19. Cached dependency
private readonly service = inject(Service);
20. In a standalone component
@Component({ standalone: true })
export class Component {
private service = inject(Service);
}
Visual: inject() Flow
┌──────────────────────────────────────────────┐
│ Angular creates the class │
│ │ │
│ ▼ │
│ Establishes injection context │
│ │ │
│ ▼ │
│ Constructor runs │
│ │ │
│ ▼ │
│ Field initializers run │
│ │ │
│ ▼ │
│ inject(Service) reads the injector │
│ │ │
│ ▼ │
│ Returns instance │
│ │
└──────────────────────────────────────────────┘
Visual: Injection Context
┌──────────────────────────────────────────────┐
│ ✅ In context: │
│ │
│ • constructor │
│ • field initializer │
│ • factory function │
│ • guard function │
│ • resolver function │
│ • runInInjectionContext │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ ❌ Out of context: │
│ │
│ • ngOnInit │
│ • ngOnDestroy │
│ • event handlers │
│ • setTimeout │
│ • Promise callbacks │
│ • after await │
│ • plain functions │
│ │
└──────────────────────────────────────────────┘
Visual: Constructor vs inject()
┌──────────────────────────────────────────────┐
│ Constructor injection │
│ │
│ export class C { │
│ constructor( │
│ private http: HttpClient, │
│ private logger: LoggerService │
│ ) {} │
│ } │
│ │
│ All dependencies in signature │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ inject() │
│ │
│ export class C { │
│ private http = inject(HttpClient); │
│ private logger = inject(LoggerService); │
│ } │
│ │
│ Each dependency is a field │
│ │
└──────────────────────────────────────────────┘
Visual: Field Initialization Order
┌──────────────────────────────────────────────┐
│ export class C { │
│ private a = inject(AService); ← 1st │
│ private b = inject(BService); ← 2nd │
│ private x = this.a.get(); ← 3rd │
│ private y = this.b.get(); ← 4th │
│ } │
│ │
│ Fields initialize in declaration order │
│ Later fields can use earlier ones │
│ │
└──────────────────────────────────────────────┘
Visual: Functional Guard
┌──────────────────────────────────────────────┐
│ export const authGuard: CanActivateFn = () =>│
│ const auth = inject(AuthService); │
│ const router = inject(Router); │
│ if (auth.isLoggedIn()) return true; │
│ return router.createUrlTree(['/login']); │
│ }; │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Angular routing calls the function │
│ │ │
│ ▼ │
│ Establishes injection context │
│ │ │
│ ▼ │
│ inject() works inside the body │
│ │
└──────────────────────────────────────────────┘
Visual: Factory with inject()
┌──────────────────────────────────────────────┐
│ Old form: │
│ │
│ { │
│ provide: CLIENT, │
│ useFactory: (http, url) => new Client(...),│
│ deps: [HttpClient, API_URL] │
│ } │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ New form: │
│ │
│ { │
│ provide: CLIENT, │
│ useFactory: () => { │
│ const http = inject(HttpClient); │
│ const url = inject(API_URL); │
│ return new Client(http, url); │
│ } │
│ } │
│ │
│ No deps array │
│ │
└──────────────────────────────────────────────┘
Visual: runInInjectionContext
┌──────────────────────────────────────────────┐
│ Outside context: │
│ │
│ ngOnInit() { │
│ inject(Service); ❌ │
│ } │
│ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ With runInInjectionContext: │
│ │
│ ngOnInit() { │
│ runInInjectionContext(this.injector, () => {│
│ const s = inject(Service); ✅ │
│ }); │
│ } │
│ │
└──────────────────────────────────────────────┘
Visual: Options Match Decorators
┌──────────────────────────────────────────────┐
│ Decorator inject() option │
│ ───────────── ───────────────── │
│ @Optional() { optional: true } │
│ @Self() { self: true } │
│ @SkipSelf() { skipSelf: true } │
│ @Host() { host: true } │
│ @Inject(T) inject(T) │
│ │
└──────────────────────────────────────────────┘
Visual: Signals + inject()
┌──────────────────────────────────────────────┐
│ export class UserListComponent { │
│ │
│ // Dependencies │
│ private userService = inject(UserService);│
│ │
│ // State │
│ users = signal<User[]>([]); │
│ │
│ // Derived │
│ count = computed(() => this.users().length);│
│ │
│ // Lifecycle │
│ async ngOnInit() { │
│ const data = await this.userService.get();│
│ this.users.set(data); │
│ } │
│ } │
│ │
│ All fields — consistent style │
│ │
└──────────────────────────────────────────────┘
Visual: Decision Flow
┌──────────────────────────────────────────────┐
│ Writing a new component? │
│ └── Use inject() in fields │
│ │
│ Writing a new service? │
│ └── Use inject() for deps │
│ │
│ Writing a guard? │
│ └── Functional + inject() │
│ │
│ Writing a resolver? │
│ └── Functional + inject() │
│ │
│ Writing a factory? │
│ └── useFactory with inject() inside │
│ │
│ Need it in a method? │
│ └── Store as field first │
│ │
│ Not in context? │
│ └── runInInjectionContext │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Meaning |
|---|---|
inject() | Read a dependency from the current injector |
| Injection context | Where inject() works |
| Field initializer | Common place for inject() |
useFactory | Factory function with inject() |
| Functional guard | CanActivateFn using inject() |
| Functional resolver | ResolveFn<T> using inject() |
| Options | optional, self, skipSelf, host |
runInInjectionContext | Enter context manually |
Injector | The injector itself |
Key takeaways:
inject()reads a dependency from the current injection context- It only works in an injection context — constructor, field initializer, factory, guard, resolver
- It doesn’t work in methods, event handlers,
setTimeout, or afterawait - Field initializers are the modern place for
inject()— cleaner than constructor parameters - Services use
inject()for their dependencies — one field per dependency - Factory functions can use
inject()— replaces thedepsarray - Functional guards and resolvers use
inject()— no class needed - Options —
{ optional, self, skipSelf, host }— match the decorators runInInjectionContextenters a context when none exists- Field initialization order follows declaration order
- Testing uses
TestBed.inject()— the same mechanism - Don’t mix constructor injection and
inject()in the same class - New code should use
inject()— constructor injection remains for legacy
Remember: inject() is the modern way to get dependencies in Angular. It’s shorter than constructor injection, works in more places, and fits signal-based code naturally. The only rule is the injection context — call it in a constructor, field initializer, factory, guard, or resolver, and Angular will find the injector. Use it in every service, component, guard, and resolver. The days of long constructor signatures and subclass parameter forwarding are over. inject() is the field-based future of Angular’s DI.
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!