Les nouveautés d'Angular 19 en 4 minutes

Angular 19 vient de sortir, et il y a beaucoup de nouveautés intéressantes : hydratation incrémentale, linkedSignal, l'API des ressources, et plein d'autres choses. Venez découvrir tout ça en moins de 4 minutes !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Création d'un resolver

Pourquoi un resolver ?

Un resolver dans Angular est comme un assistant qui prépare les données nécessaires avant que vous n'arriviez sur une page. Imaginez que vous allez au restaurant : le resolver serait comme le serveur qui prépare votre table et apporte le menu avant même que vous ne vous asseyiez.

Dans le contexte d'Angular, voici ce qu'un resolver fait :

  1. Préparation des données : Il récupère les informations nécessaires pour une route spécifique avant que la page ne soit affichée.

  2. Chargement anticipé : Au lieu d'attendre que le composant soit chargé pour commencer à récupérer les données, le resolver les obtient à l'avance.

  3. Amélioration de l'expérience utilisateur : Cela permet d'éviter d'afficher une page vide ou un indicateur de chargement, car les données sont déjà prêtes lorsque la page s'affiche.

  4. Gestion des erreurs : Si quelque chose ne va pas lors de la récupération des données, le resolver peut rediriger l'utilisateur vers une page d'erreur avant même que la route ne soit activée.

Structure

Tout d'abord, voici un exemple simple d'une structure de service de résolution :

plaintext
app/
|- user-resolver.service.ts
|- user.service.ts
|- user.interface.ts
|- app.component.ts
|- app.router.ts
|- app.config.ts
|- user-list.component.ts
plaintext
app/
|- services/
|  |- user.service.ts
|  |- user.interface.ts
|  |- resolvers/
|     |- user-resolver.service.ts
|- app.component.ts
|- app.router.ts
|- app.config.ts
|- pages/
|  |- user-list.component.ts

La fonction du resolver

ts
import { inject } from '@angular/core';
import { ResolveFn } from '@angular/router';
import { Observable } from 'rxjs';
import { User } from './user.interface';

export const userResolver: ResolveFn<User> = (
  route,
  state
): Observable<User> => {
  const userService = inject(UserService);
  return userService.get(route.params['userId']);
};
ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { User } from './user.interface';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  http = inject(HttpClient);

  get(userId: number): Observable<User> {
    return this.http.get<User>(`/api/users/${productId}`);
  }
}
ts
export interface User {
  id: number;
  name: string;
}

Ajouter le resolver à la route

Pour utiliser ce service de résolution dans votre routeur, vous devez l'inclure dans la configuration de la route en utilisant la propriété resolve. Allons dans app.router.ts :

ts
import { Routes } from '@angular/router';
import { UserListComponent } from './user-list.component';
import { userResolver } from './user-resolver.service';

const routes: Routes = [
  {
    path: 'user/:userId',
    component: UserListComponent,
    resolve: {
      user: userResolver
    }
  }
];

(astuce 1) Accéder aux données résolues dans le composant

Enfin, vous pouvez accéder aux données résolues dans le composant de la route en utilisant la propriété data de l'objet ActivatedRoute :

ts
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { User } from './user.interface';

@Component({
  selector: 'app-user-list',
  standalone: true,
  templateUrl: `
    <h1>User</h1>
    <p>{{ user.name }}</p>
  `
})
export class UserListComponent {
  private route = inject(ActivatedRoute);
  user: User = this.route.snapshot.data.user
}

Note

Le resolver est appelé avant que le composant ne soit chargé, donc les données sont déjà prêtes lorsque le composant est initialisé.

Avec un observable

Si vous souhaitez utiliser un observable, vous pouvez retourner un Observable dans le resolver.

ts
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { User } from './user.interface';

@Component({
  selector: 'app-user-list',
  standalone: true,
  imports: [AsyncPipe],
  templateUrl: `
    <h1>User</h1>
    <p>{{ (user$ | async).name }}</p>
  `
})
export class UserListComponent {
  private route = inject(ActivatedRoute);
  user$ = this.route.data.pipe(map(data => data['user']))
}

(astuce 2) Récupérer les données avec @Input()

Une autre approche élégante pour accéder aux données résolues dans votre composant est d'utiliser la propriété @Input(). Cette méthode simplifie encore davantage l'accès aux données et rend votre code plus lisible.

Voici comment vous pouvez implémenter cette approche :

ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withComponentInputBinding } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withComponentInputBinding()),
    provideHttpClient()
  ]
};

Ensuite, dans votre composant, vous pouvez utiliser @Input() pour accéder directement aux données résolues :

ts
import { Component, Input } from '@angular/core';
import { User } from './user.interface';

@Component({
  selector: 'app-user-list',
  standalone: true,
  template: `
    <h1>User</h1>
    <p>{{ user.name }}</p>
  `
})
export class UserListComponent {
  @Input() user: User = {} as User
}

AVANTAGE

Cette approche avec @Input() présente plusieurs avantages :

  • Code plus concis et plus lisible
  • Pas besoin d'injecter et gérer ActivatedRoute
  • Typage plus strict des données
  • Meilleure réutilisabilité du composant

ATTENTION

N'oubliez pas d'activer withComponentInputBinding() dans votre configuration, sinon les @Input() ne fonctionneront pas avec les données résolues.

Gestion des erreurs

Il est important de gérer les erreurs qui peuvent survenir lors de la résolution des données. Voici comment vous pouvez le faire :

ts
import { inject } from '@angular/core';
import { ResolveFn, Router } from '@angular/router';
import { Observable, catchError } from 'rxjs';
import { User } from './user.interface';

export const userResolver: ResolveFn<User> = (
  route,
  state
): Observable<User> => {
  const userService = inject(UserService);
  const router = inject(Router);

  return userService.get(route.params['userId']).pipe(
    catchError((error) => {
      router.navigate(['/error']);
      throw error;
    })
  );
};

CONSEIL

Il est recommandé de toujours gérer les erreurs dans vos resolvers pour éviter que votre application ne reste bloquée si la résolution échoue.

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !