Introduction:
Angular Signals is a groundbreaking system that offers meticulous tracking of state changes throughout an application, empowering Angular to optimize rendering updates effectively. Although Angular Signals is currently available for developer preview, it holds immense potential to revolutionize how developers manage and utilize state in their Angular projects.
What are Signals?
At its core, a signal is a value wrapper that can notify interested consumers whenever its value changes. Signals can encapsulate any data, ranging from simple primitives to complex data structures. The value of a signal is always read through a getter function, enabling Angular to monitor and track its usage throughout the application.
Signals can be either writable or read-only, depending on whether they allow direct value updates.
Writable Signals:
Writable signals provide an API for directly updating their values. To create a writable signal, you call the signal function with the initial value:
const count = signal(0);
// Accessing the signal reads its value.
console.log('The count is: ' + count());
To change the value of a writable signal, you can either use the .set()
method directly or utilize the .update()
operation to compute a new value based on the previous one:
count.set(3); // Set the count to 3
// Increment the count by 1.
count.update(value => value + 1);
Working with signals containing objects sometimes requires internal changes, such as modifying an object within the signal without replacing it entirely. To achieve this, use the .mutate
method:
const todos = signal([{ title: 'Learn signals', done: false }]);
todos.mutate(value => {
// Change the first TODO in the array to 'done: true' without replacing it.
value[0].done = true;
});
Computed Signals:
Computed signals derive their value from other signals. Define a computed signal using the computed
function and specifying a derivation function:
const count = signal(0);
const doubleCount = computed(() => count() * 2);
The doubleCount
signal depends on count
. Whenever count
updates, Angular identifies that anything dependent on either count
or doubleCount
needs an update as well.
Computed signals are lazily evaluated and memoized. The derivation function is not executed until the first time the computed signal is read. The calculated value is cached, and subsequent reads return the cached value without recomputation. Changes to dependent signals invalidate the cached value, and the recalculation occurs on the next read.
As a result, computed signals support computationally expensive derivations, making them ideal for tasks like filtering arrays.
Effects:
Effects are operations that run whenever one or more signal values change. You can create an effect with the effect
function:
effect(() => {
console.log(`The current count is: ${count()}`);
});
Effects run at least once and track signal value reads. When any of these signal values change, the effect runs again. Similar to computed signals, effects dynamically keep track of their dependencies and only track signals read in the most recent execution.
Effects always execute asynchronously during the change detection process.
Use Cases for Effects:
Effects are rarely required in most application code but can prove useful in specific circumstances, such as:
- Logging data changes for analytics or debugging purposes
- Synchronizing data with
window.localStorage
- Implementing custom DOM behavior beyond template syntax
- Performing custom rendering to a
<canvas>
, charting library, or other third-party UI library
When Not to Use Effects:
Avoid using effects for state change propagation, as this can lead to errors, circular updates, or unnecessary change detection cycles. By default, setting signals is disallowed in effects to prevent these issues, but it can be enabled when necessary.
Injection Context:
To register a new effect with the effect()
function, it requires an injection context. The simplest way to provide this is by calling effect
within a component, directive, or service constructor:
@Component({...})
export class EffectiveCounterCmp {
readonly count = signal(0);
constructor() {
// Register a new effect.
effect(() => {
console.log(`The count is: ${this.count()})`);
});
}
}
Alternatively, you can assign the effect to a field for descriptive naming:
@Component({...})
export class EffectiveCounterCmp {
readonly count = signal(0);
private loggingEffect = effect(() => {
console.log(`The count is: ${this.count()})`);
});
}
To create an effect outside the constructor, pass an Injector to effect
via its options:
@Component({...})
export class EffectiveCounterCmp {
readonly count = signal(0);
constructor(private injector: Injector) {}
initialize
Logging(): void {
effect(() => {
console.log(`The count is: ${this.count()})`);
}, { injector: this.injector });
}
}
Destroying Effects:
Effects are automatically destroyed when their enclosing context is destroyed. For instance, effects created within components are destroyed when the component is destroyed. Effects return an EffectRef
that can be used to manually destroy them via the .destroy()
method. It's crucial to clean up effects when they're no longer needed.
Advanced Topics:
Angular Signals offers additional features such as signal equality functions and reading without tracking dependencies, providing developers with further control and flexibility over state management. Additionally, effect cleanup functions allow long-running operations to be canceled when the effect is destroyed or runs again.
Conclusion:
Angular Signals presents an innovative approach to state tracking and optimization, offering signals as wrappers for values with powerful change notification capabilities. By utilizing writable and computed signals along with effects, developers can manage state efficiently and ensure responsive rendering updates. While signals are still in the developer preview, their potential impact on Angular applications is undoubtedly exciting.
Thank you for reading! I hope this article sheds light on the significant features and benefits of Angular Signals. If you have any questions or insights to share, feel free to leave a comment below. Happy coding!
Top comments (0)