Signal is a wrapper around a value, and the value can be of any type
Signals are of 2 types
- writable signals
- read-only singles
Writable signals
Signal Creation:
const count: WritableSignal - signal(0) //writable signal example, and here 0 is the initial value provided to the count
Setting the value of the signal
We can set and modify the value of signals in 2 ways
- set method
- update method
count.set(20) // updated count value from 0 to 20
count.update(prevValue => prevValue+1) //update value, also have the previous value of the signal
Reading the value of the signal
count() // we can access by calling the name of the variable with ()
read-only signals
These types of signals are created using the computed keyword
For these types of signals, we cannot update the value by using set or update methods, unlike writable signals
creation
const doubleCount = computed(() => count() *2 )
Reading the read-only signalValue
doubleCount()
Here we can see that doubleCount is dependent on the count signal value
Read-only signals are lazily evaluated
This means, unless we use somewhere in the code doubleCount(), the value is not calculated
Read-only signals memorize the values
Since the doubleCount value depends on the count value, when we read doubleCount(), then the value is calculated and it is cached, and whenever we call doubleCount () again, the cached value is returned
It recalculates the value when the count value is updated, and when we use the doublecount() somewhere in the code
*Computed signals' dependencies are dynamic *
simple example here
const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
if (showCount()) {
return `The count is ${count()}.`;
} else {
return 'Nothing to see here!';
}
});
Initially, the value of showcount is false, so the conditionalCount is dependent upon showCount only, but not on the count value
So later on if we update the count value,e the conditionalCount will not be recalculated since it is not dependent upon the count value
If we make the showCount as true and then call the conditionalCount, then the conditionalCount is dependent upon both the showCount and the count values as well, so now when the count updates and when we call the conditionalCount, then the value is recalculated
Signals with onPush
Even if we make a component change detection strategy on push, and it contains the signal, when the value of the signal is changed, Angular automatically marks the component to ensure it gets updated the next time change detection runs.
Effects
Effects run when an associated signal value changes, similar to the computed signals; it also has a dynamic dependency and runs the effect when the dependent signal value changes
An effect is run at least once, and when it runs, it tracks any signal reads and any change to the dependency will re-run the effect again
Use case of effects
to log some data when a signal value changes
to set data in local storage when the signal value changes
and some other use cases as per requirement
An effect can be written or created only in an injection context
Injection context
Let us know about the injection context here first. So, what is an injection context?
This is a concept related to dependency injection, i.e... When we use the services in the component, directives, and pipes, by injecting a service using the constructor
simple example for dependency injection
constructor(private HTTP: HttpClient){}
From Angular v14 onwards, we can use the inject keyword instead of the constructor as well
private HTTP = inject(HtppClient)
So, what is an injection context? It is an Angular runtime context that is used for dependency injection. It means we can run the dependency injection in only the injection context available
When is the injection context available
- When initializing components, directives, and pipes, (constructor method)
- Class field initialization
- In the useFactory method
- It is also available in AuthGuards directly
destroying the effects
In general, effects will be deleted automatically when associated components get destroyed
We can manually destroy the effect .destroy() method
Untracked keyword
You can prevent a signal read from being tracked by calling its getter with untracked:
example
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${counter()}`);
});
The above effect runs for both the signals, currentUSer and counter, as well
If we need the counter signal changes not to trigger this effect, then we can use something like
effect(() => {
console.log(`User set to ${currentUser()} and the counter is ${untracked(counter)}`);
});
Untracked is also useful when an effect needs to invoke some external code, which shouldn't be treated as a dependency:
effect(() => {
const user = currentUser();
untracked(() => {
this.loggingService.log(`User set to ${user}`);
});
});
Summary
Writable Signals
- Creation - const count = signal(initialValue)
- Read Signal - count()
- Change Value - count.set(newValue)
- Update Value - count.update(oldValue => oldValue + 1)
Read only signals
- Creation - const doubleCount = computed(() => count() *2)
- Read Signal - doubleCount()
- These are read-only
- No set and update method works
- Consists of Dynamic dependencies
- Lazily Evaluated
- Values are memorized
Effects
- Consists of Dynamic dependencies
- Effect runs at least once
- Runs every time a dependency signal value changes
- Concept of Injection context
- Concept of dependency injection
- Availability of Injection context
- destroying effects manually using the .destroy method
Thanks........
Happy Coding.....
Top comments (0)