Signals in Angular
In this video, we’ll explore Angular signals. They’re a major update to Angular’s reactivity model that makes it easier to track changes in components and improves overall application performance
A signal is just a wrapper around a value. Creating a signal is straightforward (see the code example below). To get the signal’s value in a template, you simply call it like a function.
Types of signals in Angular:
- writable signal – a signal whose value can be changed using the set() and update() methods.
- computed signal – a signal whose value is derived from other signals and recalculated when its dependencies change. Its value cannot be changed directly.
- linked signal – a combination of writable and computed signals: its value depends on other signals, but it can also be updated manually using set() and update().
We’ll also look at the toSignal() method and effects. toSignal() converts an observable into a signal. Effects are similar to computed signals, but Angular does not recommend using them to update signal values. Instead, they are useful for interacting with external systems such as the DOM or localStorage.
Signals also affect existing Angular features. For example, we’ll look at input signals as a replacement for the @Input decorator. Signal-based forms and httpResource are currently in experimental mode
app.ts
import {
Component,
computed,
inject,
Injector,
linkedSignal,
OnInit,
Signal,
signal,
WritableSignal,
} from '@angular/core';
import { MatToolbar } from '@angular/material/toolbar';
import { UsersService } from './features/users/services/users-service';
import { UserDto } from './core/models/User';
import { AsyncPipe, JsonPipe } from '@angular/common';
import { toSignal } from '@angular/core/rxjs-interop';
import { UserCard } from './user-card/user-card';
@Component({
selector: 'app-root',
imports: [MatToolbar, JsonPipe, UserCard],
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App implements OnInit {
protected title = 'angular-material-app';
protected myAge: WritableSignal<number> = signal(35);
protected personalInfo: Signal<string> = computed(() => `My age is ${this.myAge()}`);
private userService = inject(UsersService);
shippingOptions = signal<any[]>([
{ id: 0, name: 'Ground' },
{ id: 1, name: 'Air' },
{ id: 2, name: 'Sea' },
]);
selectedOption = linkedSignal<any[], any>({
// `selectedOption` is set to the `computation` result whenever this `source` changes.
source: this.shippingOptions,
computation: (newOptions, previous) => {
// If the newOptions contain the previously selected option, preserve that selection.
// Otherwise, default to the first option.
console.log(newOptions, previous);
return newOptions.find((opt) => opt.id === previous?.value.id) ?? newOptions[0];
},
});
protected users: Signal<UserDto[]> = signal([]);
private injector = inject(Injector);
public ngOnInit() {
this.users = toSignal(this.userService.getUsers(), {
initialValue: [],
injector: this.injector,
});
}
protected changeData() {
// this.myAge.set(36);
this.myAge.update((age) => age + 1);
this.selectedOption.set({ id: 1, name: 'Pidgin' });
}
}
app.html
<header>
<mat-toolbar color="primary">
<span>My App</span>
</mat-toolbar>
</header>
<p>App title: {{title}}</p>
<p>My age: {{myAge()}}</p>
<p>Personal info {{personalInfo()}}</p>
<p>Selected skill: {{selectedOption().name}}</p>
<!--<p>{{users() | json}}</p>-->
<p>
<button (click)="changeData()">Change</button>
</p>
<app-user-card [age]="myAge()"></app-user-card>
user-card.ts
import { ChangeDetectionStrategy, Component, input, Input, InputSignal } from '@angular/core';
@Component({
selector: 'app-user-card',
imports: [],
templateUrl: './user-card.html',
styleUrl: './user-card.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserCard {
public age: InputSignal<number> = input<number>(0);
}
user-card.html
<p>input age: {{age()}}</p>
0 Comments