Skip to content

The inject() Function

Angular’s traditional dependency injection mechanism uses constructor parameters. Every service you need must appear in the constructor signature. In complex components with many dependencies, constructors grow long. inject() is a cleaner alternative that works in more contexts.

inject() retrieves a service from the DI system and can be called as a class field initializer:

import { Component, inject } from '@angular/core';
import { MovieService } from '../../services/movie.service';
import { WatchlistService } from '../../services/watchlist.service';
import { ToastService } from '../../services/toast.service';
@Component({ /* ... */ })
export class MovieDetail {
private movieService = inject(MovieService);
private watchlist = inject(WatchlistService);
private toast = inject(ToastService);
}

Compare to constructor injection:

export class MovieDetail {
constructor(
private movieService: MovieService,
private watchlist: WatchlistService,
private toast: ToastService
) {}
}

Both work identically. inject() removes the constructor entirely when the class only needs DI — it makes the intent clearer.

Guards are functions, not classes — there is no constructor. inject() is the only way to access services in a guard:

export const watchlistGuard: CanActivateFn = () => {
const watchlist = inject(WatchlistService);
const router = inject(Router);
const toast = inject(ToastService);
if (watchlist.count() > 0) return true;
toast.show('Add a movie to your watchlist first.');
return router.createUrlTree(['/browse']);
};

This is why learning inject() matters: it unlocks DI for functional contexts where constructors do not exist.

inject() can only be called in an injection context — a place where Angular’s DI system is active:

✅ Class field initializers:

private service = inject(MyService);

✅ Constructor body:

constructor() {
const service = inject(MyService);
}

✅ Factory functions (guards, resolvers, interceptors):

const myGuard: CanActivateFn = () => {
const service = inject(MyService);
// ...
};

❌ Lifecycle hooks (too late — DI context has ended):

ngOnInit() {
const service = inject(MyService); // ❌ throws
}

❌ Async callbacks:

setTimeout(() => {
const service = inject(MyService); // ❌ throws
}, 1000);

If you need a service inside ngOnInit, inject it as a field first:

private service = inject(MyService); // ✅ injected as field
ngOnInit() {
this.service.doSomething(); // ✅ use the already-injected field
}
PatternWhen to use
Constructor injectionWhen the class has other constructor logic, or when consistency with existing code matters
inject() as fieldWhen the class exists purely for DI and logic — no other constructor code
inject() in functionRequired for guards, resolvers, and other functional contexts

In CinemaVault, all components and services use inject() as class fields. Guards use inject() in the function body. Pick one style per project and stick to it.

  1. Take a component that uses constructor injection and refactor it to use inject() for all services.
  2. Confirm the behavior is identical before and after.
  3. Create a simple guard function that uses inject() to get a service and check a condition.
  4. Try calling inject() inside ngOnInit — observe the error. Move the inject call to a class field and confirm it works.
  • inject() retrieves a service from the DI system without needing a constructor parameter.
  • Use it as a class field initializer: private service = inject(MyService).
  • Use it inside guards and other factory functions where constructors do not exist.
  • inject() can only be called in an injection context — class fields, constructors, and factory functions.
  • Do not call inject() in lifecycle hooks or async callbacks — inject as a field first, then use the field.