DEV Community

Dany Paredes
Dany Paredes

Posted on • Originally published at danywalls.com on

Understanding InjectionContext and Signal Effects

Yesterday, I was chatting with my friend Jrgen de Groot about Signals. As our conversation shifted to cover effects, he encountered the error ERROR NG0203.

Later, another friend approached me with the same issue. I decided to compose a brief article to explain why it happens.

Scenario

We have a counter app using signals to update the times of clicks, when the count signals variable gets 10, we must update a local variable (no signals) to 'You reached the limit of clicks'.

Using Effect

The signal effect is very useful for handling side effects. In our case, we have a side effect where the variable 'limitMessage' needs to be updated when the 'counts' variable reaches 10.

@Component({ selector: 'my-app', standalone: true, imports: [CommonModule, FormsModule], template: ` <h1>{{limitMessage}}</h1> <h2>{{ count()}} times</h2> <button (click)="increase()">+1</button> `,})export class App { count = signal(0); limitMessage = 'Feel free to click!'; increase() { this.count.update((p) => p + 1); }}bootstrapApplication(App);
Enter fullscreen mode Exit fullscreen mode

We run the effect in the ngOnInit lifecycle to track the changes in the count variables.

export class App implements OnInit { count = signal(0); limitMessage = 'Feel free to click!'; ngOnInit(): void { effect(() => { if (this.count() == 10) { this.limitMessage = 'You reached the limit, sorry'; } }); }
Enter fullscreen mode Exit fullscreen mode

Darn!!!! We got the error!!

Effects and Injection Context

The effect must be used in an injection context because it uses the DestroyRef service to unsubscribe when the building block is destroyed.

The Constructor

The first approach is to move the effect to the constructor, and save changes, yeah! the effect runs!!

Using runInjectionContext

To use the effect outside of the constructor, we must use the runInjectionContext function, by injecting the Injector.

export class App implements OnInit { count = signal(0); limitMessage = 'Feel free to click!'; disabled = signal(false); #injector = inject(Injector); ngOnInit(): void { runInInjectionContext(this.#injector, () => { effect(() => { if (this.count() == 10) { this.limitMessage = 'You reached the limit, sorry'; } }); }); } increase() { this.count.update((p) => p + 1); }}
Enter fullscreen mode Exit fullscreen mode

Yes! We have the effect outside of the class constructor! And no errors!

Update Signals In Effects

We are using effects without problems, but let's add a small feature connected to my effect. I want to disable the button when the count reaches 10.

I want to create the variable 'disabled' as a boolean signal and use the effect to update the value.

export class App implements OnInit { count = signal(0); limitMessage = 'Feel free to click!'; disabled = signal(false); #injector = inject(Injector); ngOnInit(): void { runInInjectionContext(this.#injector, () => { effect(() => { if (this.count() == 10) { this.limitMessage = 'You reached the limit, sorry'; this.disabled.update(() => true); } }); }); } increase() { this.count.update((p) => p + 1); }}
Enter fullscreen mode Exit fullscreen mode

Save changes then try to update the disabled signals!!! DAMM!!!!!!!!

By default, effects aren't allowed to update signals, but for these scenarios, we can disable this restriction by setting allowSignalWrite it to true.

Perfect! we are working with Signals effect without any problem!

Recap

We learn how to implement the effect in the ngOnInit lifecycle, explore the importance of the injection context, how to use runInInjectionContext to avoid errors, and update Signals in effects by disabling the default restriction using the allowSignalWrite option.

I hope this helps you when you start using Signals and effects! ;)

Source Code

Top comments (0)