| |

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:

LocationWorks
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:

AspectConstructorinject()
Boilerplateconstructor(private x: T)private x = inject(T)
Subclass forwardingRequiredNot needed
Works in factory
Composes with signals⚠️
Multiple depsLong signatureOne line each
Initialization orderConstructor runs firstFields 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. TestBed gives a controlled injector where you decide what’s provided. inject() reads from it. That’s how mocking works — replace the provider, and every inject() 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:

  • UserService uses inject() for HttpClient and LoggerService
  • UserListComponent injects UserService and uses signals
  • authGuard is a functional guard using inject()
  • userResolver is a functional resolver using inject()
  • 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

FormMeaning
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

LocationWorks
Constructor
Field initializer
Factory function
Route guard
Route resolver
provideX factory
ngOnInit
Methods
setTimeout
Promise.then
After await

Options

OptionEquivalent
{ optional: true }@Optional
{ self: true }@Self
{ skipSelf: true }@SkipSelf
{ host: true }@Host

Constructor vs inject()

AspectConstructorinject()
BoilerplateMoreLess
Works in factory
Works in guards
Subclass forwardingRequiredNot needed
Field orderingN/ADeclaration order
Signals fit⚠️
RecommendedLegacyModern

inject() in Factories

FormDeps needed
useFactory: () => { const s = inject(S); ... }
useFactory: (s) => ..., deps: [S]

inject() in Guards and Resolvers

APIType
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

HelperPurpose
runInInjectionContext(injector, fn)Run code in context
assertInInjectionContext(fn)Assert context exists
InjectorGet 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

PatternCode
Single depprivate s = inject(Service)
Optionalinject(Service, { optional: true })
Parentinject(Service, { skipSelf: true })
Owninject(Service, { self: true })
Hostinject(Service, { host: true })
Tokeninject(TOKEN)
Injectorinject(Injector)

Error Messages

ErrorCause
inject() must be called from an injection contextCalled outside context
No provider for XService not provided
NullInjectorErrorMissing provider, not optional

Migration from Constructor

BeforeAfter
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

StepCode
ConfigureTestBed.configureTestingModule({ providers: [...] })
Inject serviceTestBed.inject(Service)
Create componentTestBed.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

PitfallProblemSolution
inject() in methodNot in contextUse field
After awaitContext lostCapture before await
Optional without optionThrows if missingAdd { optional: true }
Mixing stylesConfusingPick one
Field order dependenciesValue used before setOrder fields correctly
In factory without contextRuntime errorUse useFactory in context
In event handlerNot in contextStore as field
Forgot providerNo provider errorAdd to providers
Subclass constructorInherited paramsUse inject() in fields
Testing without TestBedReal injector missingUse 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

ConceptMeaning
inject()Read a dependency from the current injector
Injection contextWhere inject() works
Field initializerCommon place for inject()
useFactoryFactory function with inject()
Functional guardCanActivateFn using inject()
Functional resolverResolveFn<T> using inject()
Optionsoptional, self, skipSelf, host
runInInjectionContextEnter context manually
InjectorThe 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 after await
  • 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 the deps array
  • Functional guards and resolvers use inject() — no class needed
  • Options{ optional, self, skipSelf, host } — match the decorators
  • runInInjectionContext enters 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!