Appearance
Depuis Angular 16, utilisez plutôt les signaux
Les signaux sont une nouvelle fonctionnalité introduite dans Angular 16 pour faciliter la réactivité. Il est plus simple de stocker un signal dans un service, de l'injecter dans un composant et de réagir aux changements de valeur.
En savoir plus sur les signaux dans l'article ici.
Stocker un état dans un service avec BehaviorSubject
Tout d'abord
Avant toute chose, vous devez vous assurer que le service HttpClient est importé dans votre application. Si ce n'est pas le cas, vous pouvez le faire en ajoutant provideHttpClient()
à la liste des fournisseurs de votre application.
Allez dans src/app/app.config.ts
:
ts
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient()
]
};
L'interface User en TypeScript de la requête
Voici un exemple d'interface qui pourrait être utilisée pour représenter les utilisateurs de cette URL:
ts
// user.interface.ts
interface User {
id: number;
name: string;
username: string;
email: string;
}
Cette interface définit les propriétés des utilisateurs qui peuvent être retournés par l'URL https://jsonplaceholder.typicode.com/users
, telles que l'identifiant de l'utilisateur, son nom, son nom d'utilisateur, son adresse électronique, etc. Vous pouvez utiliser cette interface pour stocker et manipuler les données des utilisateurs de cette URL dans votre application TypeScript.
Service UserService
Voici comment votre service pourrait être implémenté si la méthode setUsers
faisait une requête HTTP pour récupérer un nouveau tableau d'utilisateurs à partir de l'URL https://jsonplaceholder.typicode.com
:
ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, catchError, map } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { User } from '../models/user';
@Injectable({
providedIn: 'root'
})
export class UserService {
private usersSubject = new BehaviorSubject<User[]>([]);
public users = this.usersSubject.asObservable();
constructor(private http: HttpClient) { }
public setUsers() {
this.http.get<User[]>('https://jsonplaceholder.typicode.com/users')
.pipe(
catchError(() => {
this.usersSubject.error('An error occurred');
return [];
}),
map((users) => {
// traitement des données avant de mettre à jour l'état courant
return users;
})
)
.subscribe((users) => {
this.usersSubject.next(users);
});
}
}
Dans ce service, vous utilisez la méthode get
de l'objet HttpClient
pour faire une requête HTTP et récupérer un tableau d'utilisateurs. Vous utilisez ensuite les opérateurs catchError
et map
pour traiter les erreurs et les données reçues avant de mettre à jour l'état courant en appelant la méthode next
de votre instance de BehaviorSubject
.
Pourquoi utiliser BehaviorSubject au lieu de Subject ?
Le BehaviorSubject
est similaire au Subject
, mais il a une valeur courante qui est initialisée lors de sa création et qui est retournée aux abonnés qui s'abonnent après que la valeur courante a été publiée. Cela signifie que le BehaviorSubject
permet de stocker l'état courant d'une valeur et de le retourner aux abonnés qui s'abonnent après qu'une valeur a été publiée.
Voici un exemple de code qui illustre cela :
ts
import { BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject<string>('Initial value');
subject.subscribe((value) => {
console.log(value); // affiche 'Initial value'
});
subject.next('A value');
subject.subscribe((value) => {
console.log(value); // affiche 'A value'
});
Le BehaviorSubject
est particulièrement utile dans les cas où vous souhaitez stocker et partager l'état courant d'une valeur avec plusieurs composants. Par exemple, vous pouvez utiliser un BehaviorSubject
dans un service pour stocker et partager l'état courant d'un tableau d'utilisateurs avec plusieurs composants qui en ont besoin.
Pourquoi utiliser asObservable ?
La méthode asObservable
permet de retourner un observable à partir d'un Subject
, mais en empêchant les abonnés d'appeler la méthode next
sur l'observable retourné. Cela signifie que seul le Subject
original peut être utilisé pour publier de nouvelles valeurs et pour notifier les abonnés des mises à jour.
Voici un exemple de code qui illustre cela :
ts
import { Subject } from 'rxjs';
const subject = new Subject<string>();
const observable = subject.asObservable();
subject.next('A value'); // la valeur est publiée et les abonnés sont notifiés
observable.next('A value'); // génère une erreur car la méthode next est privée
observable.subscribe((value) => {
console.log(value); // affiche 'A value'
});
Lire les données dans le composant (pipe async)
Voici comment votre composant pourrait être implémenté si vous utilisiez le pipe async
pour vous abonner aux données du service :
ts
import { Component, OnInit } from '@angular/core';
import { UserService } from '../services/user.service';
import { Observable } from 'rxjs';
import { User } from '../models/user';
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
users$: Observable<User[]> = this.userService.users
constructor(private userService: UserService) { }
ngOnInit() {
this.userService.setUsers();
}
}
Dans votre template, vous pouvez utiliser le pipe async
pour afficher les données de manière asynchrone :
html
<ng-container *ngIf="(users$ | async) as users">
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</ng-container>
Le pipe async
vous permet de vous abonner aux données de manière asynchrone et de les utiliser dans votre template de manière transparente. Vous n'avez pas à vous abonner manuellement aux données dans votre composant ni à utiliser des indicateurs de chargement ou des messages d'erreur dans votre template.
Gérer les erreurs dans template
Pensez à la manière dont vous souhaitez gérer les erreurs et les cas où les données ne sont pas disponibles. Vous pouvez par exemple utiliser la directive ngIf
avec une expression de contrôle d'erreur pour afficher un message d'erreur lorsque nécessaire :
html
<ng-container *ngIf="(users$ | async) as users; else error">
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</ng-container>
<ng-template #error>An error occurred</ng-template>
Vous pouvez également utiliser la directive ngIf
avec une expression de vérification de valeur nulle pour afficher un message indiquant que les données sont en cours de chargement ou que les données sont vides :
html
<ng-container *ngIf="users$ | async as users; else loading">
<ng-container *ngIf="users.length; else noData">
<ul>
<li *ngFor="let user of users">{{ user.name }}</li>
</ul>
</ng-container>
<ng-template #noData>No data</ng-template>
</ng-container>