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() in components
Section titled “inject() in components”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.
inject() in guards
Section titled “inject() in guards”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.
Where inject() can be called
Section titled “Where inject() can be called”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}Comparing the two patterns
Section titled “Comparing the two patterns”| Pattern | When to use |
|---|---|
| Constructor injection | When the class has other constructor logic, or when consistency with existing code matters |
inject() as field | When the class exists purely for DI and logic — no other constructor code |
inject() in function | Required 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.
Exercise
Section titled “Exercise”- Take a component that uses constructor injection and refactor it to use
inject()for all services. - Confirm the behavior is identical before and after.
- Create a simple guard function that uses
inject()to get a service and check a condition. - Try calling
inject()insidengOnInit— 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.