4. Working with Effects
In this lab, we use effects to log out some messages and to display a toast when a signal changes. Also, we do some experiments to better understand Angular's Signal implementation.
🔀 Branch: For this lab, please switch to the branch 04-effects-starter:
git reset --hard
git checkout 04-effects-starter
4.1 First Steps with Effects
Here, we use effects to log out changes to signals.
-
Open the file
desserts.component.ts(src/app/desserts/desserts.component.ts) and add a constructor with an effect logging out the current search filter:import { Component, OnInit, computed, + effect, inject, signal, } from '@angular/core'; [...] ratings = signal<DessertIdToRatingMap>({}); ratedDesserts = computed(() => this.toRated(this.desserts(), this.ratings())); + constructor() { + effect(() => { + console.log('originalName', this.originalName()); + console.log('englishName', this.englishName()); + }); + } + ngOnInit(): void { this.search(); } -
Switch to the file
dessert-card.component.ts(src/app/dessert-card/dessert-card.component.ts) and add a constructor with an effect logging out some values of the current dessert:import { ChangeDetectionStrategy, Component, + effect, input, output, } from '@angular/core'; [...] ratingChange = output<number>(); + constructor() { + effect(() => { + console.log('dessert', this.dessert().englishName, this.dessert().rating); + }); + } + updateRating(newRating: number): void { this.ratingChange.emit(newRating); }Remarks: This example shows that effects can be used instead of
ngOnChanges. The same is the case for computed Signals, as shown in some prior examples. Whenever possible, prefer computed Signals. -
Start your solution and ensure yourself that all changes to the respective Signals are logged out.
💾 Please find this lab's solution in the branch 04a-add-effects (get reset --hard && git checkout 04a-add-effects). You don't have to switch to it branch, if you are fine.
4.2 Clean Up Dessert Card
To prevent too many confusing log messages on the console, remove the constructor in your dessert-card.component.ts (src/app/dessert-card/dessert-card.component.ts)::
import {
ChangeDetectionStrategy,
Component,
- effect,
input,
output,
} from '@angular/core';
[...]
ratingChange = output<number>();
- constructor() {
- effect(() => {
- console.log('dessert', this.dessert().englishName, this.dessert().rating);
- });
- }
-
updateRating(newRating: number): void {
this.ratingChange.emit(newRating);
}
💾 Please find this lab's solution in the branch 04b-clean-up (get reset --hard && git checkout 04b-clean-up). You don't have to switch to it branch, if you are fine.
4.3 Displaying a Toast
Now, let's use an effect to display a toast every time a Signal changes.
Open the file desserts.component.ts (src/app/desserts/desserts.component.ts) and inject the simple ToastService that is part of your solution. Use it to display the number of desserts every time the desserts Signal changes:
export class DessertsComponent implements OnInit {
console.log('originalName', this.originalName());
console.log('englishName', this.englishName());
});
+ effect(() => {
+ this.#toastService.show(this.desserts().length + ' desserts loaded!');
+ });
}
ngOnInit(): void {
💾 Please find this lab's solution in the branch 04c-effects (get reset --hard && git checkout 04c-effects). You don't have to switch to it branch, if you are fine.
4.4 (Not) Writing Signals
In this lab, we try to write a Signal in an effect. We also discuss why such operations should be seen as a last resort.
-
Open the file
desserts.component.ts(src/app/desserts/desserts.component.ts) and add an effect writing the English name into the original one:export class DessertsComponent implements OnInit { console.log('originalName', this.originalName()); console.log('englishName', this.englishName()); }); + + effect(() => { + this.originalName.set(this.englishName()); + }); + effect(() => { this.#toastService.show(this.desserts().length + ' desserts loaded!'); }); -
Try out your changes. You should see an exception in your developer console.
Important: Effects are not intended for propagating state. This can lead to cycles and code that is hard to manage. For this reason, Angular does not allow such operations by default.
-
Now, let's activate the Effect's
allowSignalWritesoption:export class DessertsComponent implements OnInit { console.log('englishName', this.englishName()); }); - effect(() => { - this.originalName.set(this.englishName()); - }); + effect( + () => { + this.originalName.set(this.englishName()); + }, + { allowSignalWrites: true }, + ); effect(() => { this.#toastService.show(this.desserts().length + ' desserts loaded!'); -
Try out your changes. The effect should propagate the state now.
Important: Avoid this option whenever possible in your code. Prefer derived state in the sense of computed Signals instead. For triggering asynchronous operations, Events (e.g. Browser Events or RxJS-based ones) and Signal-based stores are a great option. We will look into these concepts in further labs.
💾 Please find this lab's solution in the branch 04d-effects-writing (get reset --hard && git checkout 04d-effects-writing). You don't have to switch to it branch, if you are fine.
4.5 Exploring the Glitch-Free Property
In this lab, we are going to explore what it means when we say that Signals are glitch-free.
-
Open the file
desserts.component.ts(src/app/desserts/desserts.component.ts) and set the search parameters to an initial value. Also, using a timeout, set them to different values after 3 seconds:export class DessertsComponent implements OnInit { console.log('englishName', this.englishName()); }); - effect( - () => { - this.originalName.set(this.englishName()); - }, - { allowSignalWrites: true }, - ); + this.originalName.set('Sacher'); + this.englishName.set('Cake'); + + setTimeout(() => { + this.originalName.set('Kaiser'); + this.englishName.set('Mess'); + }, 3000); effect(() => { this.#toastService.show(this.desserts().length + ' desserts loaded!'); -
Try out your changes. Have a look at the console. You should not see intermediate values.
Remarks: You wouldn't see intermediate values either when updating the same Signal several times in a row.
💾 Please find this lab's solution in the branch 04d-effects-writing (get reset --hard && git checkout 04d-effects-writing). You don't have to switch to it branch, if you are fine.
> Follow us on X (Twitter) > More Trainings (German) > More Trainings (English)