DEV Community

Kalyan P C
Kalyan P C

Posted on

Effects and InjectionContext in Angular(v21)

Mastering the Life of an Effect: Injection Context and Beyond

To understand why an effect() behaves the way it does, we have to look at the "hidden" environment it lives in. In Angular, an effect isn't just a function; it’s a managed resource that depends on the framework's Dependency Injection (DI) system to survive and clean up after itself.

1. The Injector: The "Source of Truth"

The Injector is a container that holds instances of services and dependencies. In Angular, there is a hierarchy of injectors (Environment Injector, Component Injector, etc.).

When you create an effect, it needs to be "anchored" to an Injector. The Injector provides the effect with the services it might need and, more importantly, determines the lifetime of the effect.

2. Injection Context: The "Where" and "When"

Injection Context is a specific state during code execution where the inject() function is available. Also think of it as the period of time when the object(Component or Directive etc) is constructed.
By default, you have an Injection Context in:

  • The constructor of a class.
  • Field initializers (e.g., myService = inject(MyService)).
  • The factory function of a Provider.

Why does effect need it?
When you call effect(() => { ... }) in a constructor, Angular implicitly looks for the current Injection Context to find and "inject" the DestroyRef. It uses this to know exactly when to "kill" the effect so it doesn't cause memory leaks.

3. The inject() Function: The Modern Fetcher

The inject() function is the programmatic way to get a dependency from the current Injection Context.

If you try to create an effect inside a regular method (like a button click), it will fail because that method doesn't have an Injection Context.

// ✅ WORKS: In the constructor/class init
count = signal(0);
myEffect = effect(() => console.log(this.count())); 

// ❌ FAILS: Outside injection context
updateData() {
  effect(() => { ... }); // Error: inject() must be called from an injection context
}
Enter fullscreen mode Exit fullscreen mode

To fix this, you have to "capture" the injector manually:

export class MyComponent {
  private injector = inject(Injector); // Capture the injector in the constructor

  startManualEffect() {
    // We pass the captured injector to the effect
    effect(() => {
      console.log('Manual effect running!');
    }, { injector: this.injector }); 
  }
}
Enter fullscreen mode Exit fullscreen mode

4. DestroyRef: The "Clean-up Crew"

DestroyRef is a modern replacement for the OnDestroy lifecycle hook. It allows you to register cleanup logic anywhere in your code, not just in the class definition.

When an effect is created, it internally "subscribes" to the DestroyRef of its context. When the component or service holding that effect is destroyed, the DestroyRef fires, and the effect is automatically stopped.

Manual Cleanup with DestroyRef
If you are building a custom utility that uses effects, you might use DestroyRef like this:

const dr = inject(DestroyRef);
const sub = mySignal.subscribe(...);

dr.onDestroy(() => {
  console.log('Cleaning up resources!');
  sub.unsubscribe();
});
Enter fullscreen mode Exit fullscreen mode

The runInInjectionContext Helper
Sometimes you have an Injector but you don't want to pass it as an option to every single function. You can "force" a context:

import { runInInjectionContext } from '@angular/core';

runInInjectionContext(this.injector, () => {
  // Anything called in here now has access to inject()
  effect(() => console.log('I have context!'));
});
Enter fullscreen mode Exit fullscreen mode

Code PlayGround:

  1. Understand how Injection context works
  2. Understand how Effects work Injection context

Key points to remember:

  • Effects are tied to the Injector.
  • Injectors use DestroyRef to clean up.
  • inject() only works when the Injection Context is active.
  • If you create an effect outside a constructor, you must provide the injector manually.

Happy coding!!!

Top comments (0)