In Angular, what is the difference between injecting with inject() and injecting through constructor parameters, and which does the style guide recommend?
answer
- same injector, same resolution
- explicit token vs parameter type
- inheritance without super arguments
- field order matters
- usable in plain functions
basics
~20 sBoth resolve dependencies from the same injector in the same way. inject() is called in a field initializer or constructor, giving better type inference, no super() forwarding and use in plain functions; Angular's style guide prefers it.
solid answer
~40 sConstructor injection declares dependencies as parameters, `constructor(private cart: Cart) {}`, and the compiler turns the parameter types into a factory. `inject(Cart)` asks for the same thing explicitly, usually in a field initializer: `private cart = inject(Cart)`. Resolution is identical — same injector, same lookup — so the difference is ergonomics. `inject()` takes the token as an argument, so `InjectionToken`s need no `@Inject` decorator and the type is inferred; subclasses do not have to repeat and forward the base class's dependencies through `super(...)`; and functions such as guards and provider factories can call it, which constructors cannot help with. Angular's style guide recommends `inject()`. The catch is that it only works in an injection context, and field initializers run top to bottom, so a field that uses an injected dependency must be declared after it.
code
ts · 23 linesimport { Component, Injectable, InjectionToken, inject, signal } from '@angular/core';
export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL', { factory: () => '/api' });
@Injectable({ providedIn: 'root' })
export class Cart {
readonly items = signal<string[]>([]);
}
// Base class resolves its own dependencies.
export abstract class PageBase {
protected readonly baseUrl = inject(API_BASE_URL); // typed as string, no @Inject
}
@Component({
selector: 'app-checkout-page',
template: `<p>{{ itemCount }} items, API at {{ baseUrl }}</p>`,
})
export class CheckoutPage extends PageBase {
private readonly cart = inject(Cart); // injected fields first
protected readonly itemCount = this.cart.items().length; // safe: cart already assigned
// No constructor needed: nothing to forward to super().
}go deeper
Know both syntaxes, that they resolve the same way, and that the style guide prefers inject() in field initializers.
Explain the practical advantages — token typing, no @Inject, inheritance without forwarding — and the declaration-order trap.
Use inject() to simplify base classes and shared helpers while keeping calls inside construction time, and review for fields that read dependencies too early.
Set one injection style for the codebase, schedule the mechanical migration, and make field-order and context rules part of review guidance.
## Two ways to ask for a dependency Angular's **dependency injection** (DI) creates components, directives and services and supplies what they need. A class can state its needs in two ways. **Constructor parameters** — the classic form: ```ts export class CheckoutPage { constructor(private readonly cart: Cart, @Inject(API_BASE_URL) private readonly baseUrl: string) {} } ``` The Angular compiler reads the parameter types and generates a factory that resolves each one. A parameter whose type is not a usable token — a string resolved through an `InjectionToken`, for instance — needs the `@Inject(TOKEN)` parameter decorator. **The `inject()` function** — the current recommendation: ```ts export class CheckoutPage { private readonly cart = inject(Cart); private readonly baseUrl = inject(API_BASE_URL); // typed as string } ``` Both forms are resolved by the same injector with the same rules. The style guide says `inject()` "works the same way as constructor parameter injection"; the reasons to prefer it are about code, not behaviour. ## Why the style guide prefers `inject()` | Concern | Constructor parameters | `inject()` | |---|---|---| | Token that is not a class | needs `@Inject(TOKEN)` | pass the token directly | | Type of the result | written by hand | inferred from the token | | Base class with dependencies | subclass must accept and forward them via `super(...)` | base class resolves its own; subclass calls `super()` with no DI arguments | | Comments per dependency | awkward inside a parameter list | natural on each field | | Fields computed from a dependency | may need separate declaration and assignment | can be initialized inline, after the injected field | | Plain functions (guards, factories) | not applicable | works in their injection context | The inheritance row is often the deciding one in real codebases: adding a dependency to a base class with constructor injection forces edits to every subclass constructor. ## Same moment, same injector It helps to see why the two forms behave identically. For a class with constructor parameters, the compiler generates a factory that, while Angular is creating the instance, asks the current injector for each parameter's token and passes the results to `new`. A field initializer that calls `inject()` runs during that same creation, with that same injector current. For a component, that is its element injector, falling back through ancestor elements to the environment injectors. Nothing about lookup rules, scoping or instance sharing changes between the forms — which is why switching styles is a mechanical refactor rather than a behavioural change. ## The rules that come with `inject()` 1. **Injection context only.** `inject()` works while Angular is creating the instance — in field initializers and in the constructor body — and in other injection contexts such as provider factories. Calling it later, in a lifecycle hook or event handler, throws NG0203. 2. **Declaration order.** Field initializers run top to bottom. In ```ts total = this.cart.items().length; // runs first: this.cart is undefined private readonly cart = inject(Cart); ``` the first line fails with a `TypeError`, not a DI error — the context is fine, the field is just not assigned yet. Put injected fields first. 3. **Explicit tokens.** Because the token is an argument, refactors that change a parameter's type no longer silently change what is injected. ## When you still meet constructor injection Most production code written before `inject()` became the norm uses constructor parameters, and it remains fully supported for classes decorated with `@Injectable`, `@Component`, `@Directive` and `@Pipe`. The v22 `@Service()` decorator is designed around `inject()`; its guide lists constructor-based DI as unsupported. You can mix both forms in one class, though a consistent style is easier to read. Converting an existing codebase is a mechanical migration with its own tooling. ## Practical guidance - Default to `inject()` in field initializers, injected fields first. - Keep constructors for logic that needs several injected values together, or drop them entirely. - In base classes, prefer `inject()` so subclasses stay free of DI plumbing. - Treat NG0203 as a sign that `inject()` moved out of construction time, not as a reason to go back to constructor parameters.
- Is inject() slower or faster than constructor injection at runtime?Neither in any way that matters: both end up asking the same injector for the same token while the instance is being created. The choice is about readability, typing and inheritance, which is why the style guide frames its recommendation as style advantages rather than performance.
- A subclass constructor calls super() and then uses a field the base class set with inject(). Is that safe?Yes. Base-class field initializers run during the `super()` call, still inside the injection context of the subclass's creation, so by the time the subclass constructor body continues the base fields are assigned. Only reading a field before its own initializer has run is a problem.
saying these in an interview costs you the question
- inject() resolves from a different injector than constructor parameters
- inject() can be called anywhere in a component, including ngOnInit
- Angular reorders fields so injected ones are always ready first
- Subclasses must forward the base class's inject() dependencies to super()
- Constructor injection is removed in current Angular