Keeping up with the most recent technology and tools is crucial in the quickly changing field of web development. The well-known front-end framework Angular is still setting the standard for innovation as it introduces several interesting features that should make development easier, improve performance, and improve the user experience as a whole.
In this post, we'll dig into Angular 16's most notable features and examine how they're likely to change the way we create web applications. These new capabilities are intended to make your development process easier, your code more effective, and your applications more useful whether you're an experienced Angular developer or just beginning your adventure.
Standalone Components:
Standalone Components provide a simplified way to build Angular applications. Standalone components, directives, and pipes aim to streamline the authoring experience by reducing the need for NgModule
ng generate component button --inline-template --standalone
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-button',
standalone: true,
imports: [CommonModule],
template: `
<button class="app-button">
<ng-content></ng-content>
</button>
`,
styleUrls: ['./button.component.css']
})
export class ButtonComponent {
}
Angular Signals
Angular Signals is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates.
What are signals?
A signal is a wrapper around a value that can notify interested consumers when that value changes. Signals can contain any value, from simple primitives to complex data structures.
A signal's value is always read through a getter function, which allows Angular to track where the signal is used.
Signals may be either writable or read-only.
//Writable count = signal(0); //(property) App.count: SettableSignal<number> // (alias) signal<number>(initialValue: number, equal?: ValueEqualityFn<number>): SettableSignal<number> //Read-only //What if there is another value that depends on the values of other signals, and needs to be recalculated 🔄 whenever any of those dependencies changes? //In this case, we can use a computed() function to create a new signal that automatically updates whenever its dependencies change. double = computed(() => this.count() * 2); //(property) App.double: Signal<number> //(alias) computed<number>(computation: () => number, equal?: ValueEqualityFn<number>): Signal<number>
Signals are primitives, so we can use them outside the component class
const myValue = signal<number>(10000);
The signal
function is a TypeScript function that creates a Signal. It takes two arguments:
initialValue
: Represent the initial value of the signal, and it can be of any typeT
options
is an object of theCreateSignalOptions
type that includes anequal
method for comparing two values of typeT
. If theoptions
object is not provided when creating a signal, thedefaultEquals
function will be used instead. ThedefaultEquals
function compares two values of the same typeT
using a combination of the===
operator and theObject.is
method
The signal
function returns a WritableSignal<T>
. Signal are a getter function, but the type WritableSignal
give us the possibility to modify the value by three methods :
set
[set(value: T): void] for replacement (set the signal to a new value, and notify any dependents)
this.count.set(0);
update
[update(updateFn: (value: T) => T)] for deriving a new value (Update the value of the signal based on its current value, and notify any dependents), The update operation uses the set() operation for performing updates behind the scenes.
this.count.update(number => number + 1)
mutate
[mutate(mutatorFn: (value: T) => void)] for performing internal mutation of the current value (Update the current value by mutating it in-place, and notify any dependents)
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; });
Signal is not just a value that can be modified, it is more than that, Signal is a reactive value 🔃 and is a producer that notifies consumers(dependents) when it changes.
So dependents in Signal are any code that has registered an interest in the Signal’s value and wishes to be notified whenever the Signal’s value changes. When the Signal’s value is modified, the Signal will notify all of its dependents, allowing them to react to the change in the Signal’s value. This makes the Signal is the core element of a reactive application, as it allows different parts of the application to automatically update in response to changes in data.
effect
Sometimes, when a signal has a new value, we may need to add a side effect. To accomplish this, we can use the effect() function.
Effect schedules and runs a side-effectful function inside a reactive context.
export function effect(
effectFn: () => EffectCleanupFn | void, options?: CreateEffectOptions): EffectRef
The function inside the effect will re-evaluate with any change that occurs in the signals called inside it. Multiple signals can be added to the effect function.
when we declare an effect function, the effectFn passed as an argument will be added to the list of the consumers of any signals used by the effectFn, such as movies in our example. (Signals used by the effectFn will be the producers).
Then, when the signal has a new value by using the set, update, or mutate operators, the effectFn will be re-evaluated with the new signal value(The producer notifies all consumers of the new values).
constructor() {
effect(() => {
console.log('Count changed', this.count());
console.log('Count doubled', this.double());
});
}
The effect()
function returns an EffectRef
, which is a global reactive effect that can be manually destroyed. An EffectRef
has a destroy operation.
Use cases for effects
Effects are rarely needed in most application code but may be useful in specific circumstances. Here are some examples of situations where an effect
might be a good solution:
Logging data being displayed and when it changes, either for analytics or as a debugging tool
Keeping data in sync with
window.localStorage
Adding custom DOM behavior that can't be expressed with template syntax
Performing custom rendering to a
<canvas>
, charting library, or other third-party UI library
Destroying effects
When you create an effect, it is automatically destroyed when its enclosing context is destroyed. This means that effects created within components are destroyed when the component is destroyed. The same goes for effects within directives, services, etc.
Effects return an EffectRef
that can be used to destroy them manually, via the .destroy()
operation. This can also be combined with the manualCleanup
option to create an effect that lasts until it is manually destroyed. Be careful to actually clean up such effects when they're no longer required.
When not to use effects
Avoid using effects for the propagation of state changes. This can result in ExpressionChangedAfterItHasBeenChecked
errors, infinite circular updates, or unnecessary change detection cycles.
Because of these risks, setting signals is disallowed by default in effects, but can be enabled if necessary.