Angular is changing a lot these days. One of the major changes that most developers are already aware of is how to inject dependencies.
// Before
@Component()
export class ProductPage {
constructor(private readonly productsApi: ProductsApi) {}
}
// Now
@Component()
export class ProductPage {
private readonly productsApi = inject(ProductsApi);
}
From compilation to runtime error
The constructor-based injection was more limited, but it was safe on the compilation level.
On the other hand, inject() and similar functions must be called from an injection context. Using them outside an injection context will compile just fine, but it will throw a runtime error that most developers have already encountered: the NG0203 error.
This is a setback, as it is more difficult to debug, and as errors can be deployed and can happen in production.
To inject or not to inject?
The injection context is available in:
- Angular classes properties initializers and constructors
- guards, resolvers, interceptors and routing options
- factories
- some application providers
- explicit injection context via
runInInjectionContext()
@Component()
export class ProductPage {
private readonly productsApi = inject(ProductsApi); // OK
constructor(): void {
inject(ProductsApi); // OK
}
}
The injection context is not available in:
- any other methods than constructor (including component lifecycle methods and events methods)
- asynchronous callbacks or after an await
- non-Angular classes or functions
@Component()
class ProductPage {
constructor() {
somePromise.then(() => {
inject(ProductsApi); // Runtime error
});
}
ngOnInit(): void {
inject(ProductsApi); // Runtime error
}
onSubmit(): void {
inject(ProductsApi); // Runtime error
}
async saveProduct(): Promise<void> {
await postToApi();
inject(Router); // Runtime error
}
}
function save(): void {
inject(ProductApi); // Runtime error
}
More (and more) context-sensitive functions
inject() is not the only function affected. Most of the new Angular functions require an injection context, notably:
effect()- signal
form() -
resource()andrxResource() takeUntilDestroyed()-
toSignal()andtoObservable()
Third-party libraries based on signals or observables are often affected too.
So the injection context is becoming a central concept of Angular.
Lint rules to the rescue
For these reasons, I created and published new lint rules: angular-eslint-injection-context.
Following the 2 steps README on GitHub, it just takes a minute to install and configure, and then:
- the editor now shows when
inject()and similar functions are used in the wrong places - if the lint is included in the CI, the project is protected from deploying injection errors in production
It reconciles the flexibility of the new dependency injection system with the safety and reliability of a compilation-oriented approach.
Note
Find this post and tool useful? Iām open to freelance & full-time opportunities. Feel free to reach out on LinkedIn or Bluesky.
Top comments (0)