Cours Angular · Module 9: HttpClient

La barrière sur la route - les interceptors HTTP

22 min de lecture
Dans cette leçon8

Tu sais déjà accrocher le sceau du clan à une lettre : tu construis un HttpHeaders avec set() et tu remets le jeu complet dans les options de la requête. Sur une seule méthode, c'est un travail de deux lignes. L'ennui commence le jour où le château instaure un nouvel usage.

Et cet usage dit ceci : tout coursier qui franchit les murailles porte sur lui un jeton de garde, un token dans notre langue. Pas seulement celui qui part chercher la liste des samouraïs - aussi celui qui emmène la recrue, celui qui porte l'ordre de promotion, celui qui demande une radiation du registre. Ton registre du clan compte cinq méthodes, donc tu recopies cinq fois la même ligne avec l'en-tête Authorization. Le service de l'armurerie en compte quatre, ce qui fait déjà neuf copies. Demain, un second en-tête s'ajoute et tu corriges neuf endroits d'un coup. Après-demain, quelqu'un écrit une dixième méthode et oublie le sceau, le serveur répond 401, et la soirée passe à chercher une seule ligne manquante.

Avec les boucliers de la leçon sur les pannes, c'est exactement pareil. Un jeton périmé se présente de la même façon dans chaque méthode : un statut 401 et l'obligation de renvoyer l'utilisateur vers l'écran de connexion. Recopiée dans neuf méthodes, la même gestion d'erreur est le même travail accompli neuf fois - et ce sont neuf endroits où l'on peut le casser.

Sur la route du Tokaido, personne n'a résolu cela en installant un scribe dans chaque maison. On y dressait une barrière : un poste en travers du chemin, par lequel passe quiconque emprunte cette route. Un seul endroit et un seul usage - appose le sceau, inscris le passage au registre, laisse filer. En Angular, une telle barrière s'appelle un interceptor, et c'est d'elle que parle cette leçon. C'était là la promesse de la leçon sur les options de la requête : un unique endroit par lequel passe chaque requête de l'application.

Ce qu'une barrière est, et ce qu'elle n'est pas

Une barrière est une fonction que tu n'appelles jamais toi-même. C'est Angular qui l'appelle, et à un instant très précis : la requête est déjà construite par HttpClient avec tout ce que tu lui as donné, mais elle n'est pas encore partie sur le réseau. La fonction reçoit cette requête toute prête entre les mains et peut la remplacer avant l'envoi. Elle reçoit aussi un moyen de la transmettre plus loin et, en échange, un flux d'événements qui reviennent de l'autre côté, flux dans lequel il lui est également permis de regarder. Un seul enregistrement couvre toutes les requêtes de l'application, sans ajouter quoi que ce soit dans les services.

Retiens-le en une phrase, car c'est le cœur de toute la leçon : un interceptor sert à modifier les requêtes et les réponses de façon globale, et la raison la plus fréquente d'en dresser un, c'est d'ajouter le token à chaque requête.

Il vaut la peine de séparer cela tout de suite de trois choses avec lesquelles on confond la barrière. Premièrement, ce n'est pas un rempart destiné à bloquer les requêtes indésirables. Dans ton application, ce sont tes propres lignes de code qui envoient les requêtes, il n'y a donc rien à filtrer là, et un interceptor finit presque toujours par transmettre la requête plus loin. Refuser l'entrée, c'est le travail des gardes de routes que tu postais aux portes dans le module sur le routage, et celui du serveur lui-même, de l'autre côté. Deuxièmement, une barrière n'est pas un mécanisme de mise en cache des réponses : la mémoire des données déjà récupérées, tu l'as construite dans la leçon sur les boucliers, à l'intérieur du service, avec une Map et l'opérateur shareReplay - et pas un seul interceptor n'y a pris part. La barrière elle-même ne retient rien. Troisièmement, elle ne s'occupe pas de la compression des données : celle-là, le navigateur et le serveur se la négocient entre eux par l'en-tête Content-Encoding, et ton code ne voit même pas les octets sur lesquels elle porterait.

Ne confonds pas non plus la barrière avec le garde de la porte. Le garde de route répondait une fois pour une navigation et disait "je laisse passer" ou "demi-tour". La barrière, elle, se tient sur le chemin et accompagne chaque requête prise séparément, et sa réponse n'est pas un verdict mais du trafic laissé passer. Elles n'ont en commun que ceci : toutes deux sont de simples fonctions, et toutes deux peuvent attraper un service par inject().

La forme d'une barrière : HttpInterceptorFn

Pour que TypeScript sache quelle fonction il doit attendre ici, le paquet @angular/common/http exporte un alias de type tout prêt, nommé HttpInterceptorFn. Tu en signes ta constante après les deux points, et tu n'as besoin d'écrire aucune annotation à l'intérieur - les types des deux paramètres, TypeScript les déduira tout seul.

Les paramètres sont au nombre de deux, et tu utiliseras les deux dans chaque barrière que tu écriras. Le premier, traditionnellement appelé req, c'est la requête toute prête : un objet HttpRequest avec sa méthode, son adresse, ses en-têtes et sa charge. Le second, next, c'est la fonction qui transmet plus loin - appelée avec une requête, elle la lance sur le tronçon de route suivant et renvoie le flux des événements qui reviendront de ce chemin. Ce flux est en même temps ce que ta barrière doit rendre vers l'extérieur.

Commençons par une barrière qui n'arrête personne, pour voir d'abord la construction elle-même, sans logique à l'intérieur.

1// interceptors/audit.interceptor.ts
2import { HttpInterceptorFn } from '@angular/common/http';
3
4export const auditInterceptor: HttpInterceptorFn = (req, next) => {
5  return next(req);
6};

Cette barrière prend la requête et la rend intacte, et son flux de sortie est exactement ce qu'a renvoyé next. Remarque ce qui n'y est pas : pas de classe, pas de décorateur, pas de mot new. C'est une simple constante avec une fonction fléchée, exactement comme pour les gardes de routes. Plus important encore, cette fonction ne s'exécutera pour l'instant pas une seule fois - le fichier est posé dans le projet et Angular n'en sait rien, parce que personne ne l'a encore déclarée nulle part.

Le nom du type, retiens-le à la lettre, car trois autres sonnent tout aussi raisonnablement et chacun échoue pour une raison différente. Je les ai passés au compilateur TypeScript sur Angular 19. Le nom InterceptorFunction n'existe tout simplement pas - l'import se termine par l'erreur TS2305, qui signale l'absence d'un tel export dans le module @angular/common/http. Le nom HttpHandler existe, mais il décrit autre chose : c'est le type de l'objet qui transmet plus loin dans l'ancienne variante à classes, un objet muni d'une méthode handle(), et non le type de ta fonction ; en signer une fonction fléchée se termine par l'erreur TS2322. Il existe enfin HttpInterceptor sans suffixe, et c'est là le vrai piège, car il appartient à la variante précédente : l'interface qu'implémentait une classe dotée d'une méthode intercept(), enregistrée ensuite par withInterceptorsFromDi(). Une fonction fléchée ne se glisse pas non plus sous cette interface - à nouveau TS2322. La variante fonctionnelle est apparue dans Angular 15 et c'est elle que je te recommande : elle est plus courte, elle n'exige ni classe ni entrée chez les fournisseurs, et inject() t'y donne accès à tout ce pour quoi il fallait autrefois passer par le constructeur.

Dresser la barrière sur la route

La fonction seule n'interceptera rien tant que tu ne lui auras pas indiqué sa place sur le chemin. Cette place est unique et tu la connais depuis le premier relais de ce module : l'appel à provideHttpClient() dans le fichier de configuration de l'application. Les barrières s'y déclarent avec la fonction withInterceptors(), qui prend un tableau de fonctions.

1// app.config.ts
2import { ApplicationConfig } from '@angular/core';
3import { provideHttpClient, withInterceptors } from '@angular/common/http';
4import { auditInterceptor } from './interceptors/audit.interceptor';
5
6export const appConfig: ApplicationConfig = {
7  providers: [
8    provideHttpClient(withInterceptors([auditInterceptor]))
9  ]
10};

Examine cette unique ligne élément par élément, car tu auras à la reconstituer : d'abord le nom provideHttpClient, puis la parenthèse ouvrante, puis le nom withInterceptors, puis le tableau des barrières, et à la fin la parenthèse qui referme tout l'appel. À partir de cet instant, chaque requête envoyée par HttpClient passe par auditInterceptor - qui laisse toujours tout le monde filer sans changement, mais qui participe désormais au voyage.

Le plus important, c'est ce qui n'a pas changé. Le registre du clan, l'armurerie et tous les autres services ont exactement la même allure qu'hier : pas une ligne nouvelle, pas un import nouveau. Le service ignore qu'un poste s'est dressé sur la route, et c'est là toute la force de cette solution. Les composants n'ont pas changé non plus, puisqu'ils parlent au service et non au réseau.

Deux détails de ce tableau savent faire mal. Auprès du nom auditInterceptor il n'y a pas de parenthèses - tu passes la fonction elle-même et non son résultat, exactement comme pour les gardes de routes. L'écriture [auditInterceptor()] essaierait d'appeler la barrière immédiatement, au moment du chargement du fichier de configuration, et cette faute-là, justement, TypeScript l'attrape pour toi : je l'ai vérifié au compilateur, il répond par l'erreur TS2554, disant qu'on attendait deux arguments et qu'on n'en a fourni aucun. Tu ne glisseras pas non plus dans ce tableau une classe à l'ancienne - withInterceptors n'accepte que des fonctions, et une classe s'y termine par l'erreur TS2322 pour absence de signature correspondante.

Le sceau sur chaque rouleau

Passons au vrai travail de la barrière. Convenons que le projet contient un service AuthService doté d'une méthode getToken(), qui rend le jeton courant ou bien null quand personne n'est connecté - le même service auquel tu demandais le sceau à la porte, dans le module sur le routage. Tu attraperas ce service avec la fonction inject() du paquet @angular/core, car le corps d'un interceptor est un contexte d'injection : c'est Angular qui appelle cette fonction et qui prépare lui-même ce contexte.

Avant d'ajouter le moindre en-tête, tu dois pourtant connaître la règle qui gouverne tout ici : HttpRequest est immuable. Impossible d'y remplacer quoi que ce soit sur place. L'affectation req.headers = ... n'est pas une question de bon style, mais une erreur de compilation TS2540 pour affectation à une propriété en lecture seule ; je l'ai vérifié aussi pour req.url et le résultat est le même. La raison est pratique : la même requête traverse toute une file de barrières et, lors d'une nouvelle tentative, elle peut reprendre la route une seconde fois. Si chaque poste pouvait la gratter à sa façon, plus personne ne saurait quelle version de l'ordre est réellement sortie des murailles.

Au lieu de gratter, tu fais donc une copie. C'est à cela que sert la méthode clone() appelée sur la requête : elle renvoie un nouvel objet HttpRequest, y recopie tout ce que tu ne remplaces pas - la méthode, l'adresse, la charge - et y applique les champs que tu lui remets dans un objet.

1// interceptors/auth.interceptor.ts
2import { HttpInterceptorFn } from '@angular/common/http';
3import { inject } from '@angular/core';
4import { AuthService } from '../services/auth.service';
5
6export const authInterceptor: HttpInterceptorFn = (req, next) => {
7  const authService = inject(AuthService);
8  const token = authService.getToken();
9
10  if (token) {
11    const authReq = req.clone({
12      headers: req.headers.set('Authorization', `Bearer ${token}`)
13    });
14    return next(authReq);
15  }
16
17  return next(req);
18};

J'ai fait tourner cette barrière sur une vraie chaîne HTTP d'Angular 19, avec un serveur de test. Le serveur a reçu une requête portant l'en-tête Authorization avec la valeur Bearer suivie du jeton : le sceau est donc bien arrivé à destination. J'en ai profité pour vérifier le point le plus important : l'objet req d'origine n'a toujours pas d'en-tête Authorization après toute l'opération. La copie est née à côté, c'est elle qui a reçu l'en-tête, et c'est elle que nous avons remise à next. L'adresse et la méthode sont passées dans la copie sans changement - clone() ne t'oblige pas à les réécrire.

À l'intérieur de la copie, une seconde immutabilité est à l'œuvre, celle de la leçon sur les options de la requête : req.headers.set(...) n'ajoute rien non plus sur place, il renvoie un nouveau jeu d'en-têtes. C'est pourquoi le résultat de cet appel part directement dans clone(). Si, sur une ligne à part, tu écrivais seulement req.headers.set('Authorization', ...) sans rien faire du résultat, la requête partirait sans le sceau, et le compilateur ne dirait pas un mot.

La barrière elle-même n'est encore dressée nulle part tant que tu ne l'as pas déclarée - et la déclaration ressemble exactement à celle d'il y a un instant, avec seulement un autre nom dans le tableau : provideHttpClient(withInterceptors([authInterceptor])).

Je vais encore désamorcer trois malentendus autour de clone(), car ils circulent obstinément dans le dojo. Le premier : la copie enverrait la requête plus vite. Non - recopier quelques champs n'accélère rien et, si l'on veut être exact, cela coûte même un peu de travail ; clone() existe pour qu'on puisse changer quoi que ce soit, pas pour aller plus vite. Le deuxième : sans clone(), la barrière ne s'enregistrerait pas. Non - l'enregistrement est uniquement l'affaire de withInterceptors et ne sait rien du clonage ; l'auditInterceptor du début de la leçon n'avait pas un seul clone() et il s'est déclaré sans broncher. Le troisième : ce serait une simple convention et l'on pourrait modifier directement. Non, on ne peut pas, et ce n'est pas une entente entre développeurs mais une affaire de type : le compilateur rejette l'affectation à headers par l'erreur TS2540.

Quand un service n'est nécessaire que pour lui poser une seule question, il est permis de replier ces deux premières lignes en une seule. Le résultat d'inject() peut être interrogé immédiatement, d'un point.

1const token = inject(AuthService).getToken();

Cette écriture signifie exactement la même chose que les deux lignes précédentes, et c'est elle qui est la plus fréquente en pratique - la constante authService ne servait plus à rien ensuite. Fais bien attention, en revanche, à l'endroit où cette ligne se tient : dans les toutes premières lignes du corps de la barrière. inject() ne peut être appelée que de façon synchrone, dans un contexte d'injection, alors ne la déplace pas dans une fonction qui s'exécutera plus tard - tu verras dans un instant ce qui arrive quand quelqu'un essaie. Le reste de l'intérieur d'une barrière suit toujours le même ordre : d'abord obtenir le jeton, ensuite faire la copie de la requête avec l'en-tête, ensuite remettre cette copie à next.

Ce qu'on voit sur le chemin du retour

Jusqu'ici nous n'avons regardé que dans un sens. Or next(req) n'est pas une impasse : il renvoie un flux, et ce flux, tu n'es pas obligé de le rendre intact. Il est permis de le brancher sur pipe() et d'y ajouter des opérateurs, exactement comme tu le faisais dans le module sur les éclaireurs ninja.

Construisons donc un registre des passages : une barrière qui mesure le temps que le coursier a passé sur la route. Pour la simple observation, on dispose de l'opérateur tap, qui laisse filer les valeurs sans les modifier tout en permettant de faire quelque chose au passage. Les événements se reconnaissent au champ type, et les noms de leurs espèces viennent de l'énumération HttpEventType, que tu connais de la leçon sur les options de la requête - celui qui nous intéresse est HttpEventType.Response, c'est-à-dire la fin du voyage.

1// interceptors/logging.interceptor.ts
2import { HttpEventType, HttpInterceptorFn } from '@angular/common/http';
3import { tap } from 'rxjs';
4
5export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
6  const started = Date.now();
7
8  return next(req).pipe(
9    tap(event => {
10      if (event.type === HttpEventType.Response) {
11        console.log(req.method, req.url, Date.now() - started, 'ms');
12      }
13    })
14  );
15};

Après exécution, une ligne apparaît dans la console pour chaque requête : la méthode, l'adresse et un nombre de millisecondes. Je ne te donnerai pas ce nombre, car il dépend du réseau et du serveur, et il change d'un essai à l'autre : sur le serveur de test local que j'ai utilisé, il tenait sur un seul chiffre, alors qu'une vraie API distante en compte bien davantage. Plus important est ce qui n'a pas changé : la requête est partie sur la route telle qu'elle serait partie sans barrière, la réponse est revenue intacte jusqu'au composant, et tap n'a rien remplacé dans le flux - il ne fait que regarder.

Un détail surprend à la première rencontre. J'ai vérifié quels événements traversent la barrière lors d'un simple get(), et il y en avait deux : d'abord Sent, puis Response. La barrière voit tout le trafic de la route, alors même que le composant n'a demandé que le contenu de la réponse - le tri des événements se fait plus haut, après les interceptors. C'est pourquoi la condition sur HttpEventType.Response n'est pas un ornement : sans elle, l'inscription au registre partirait aussi au moment de l'envoi, c'est-à-dire avant qu'il y ait quoi que ce soit à mesurer.

Retiens au passage à quel moment une barrière prend la parole. Je l'ai vérifié avec un compteur : après la construction du flux, mais avant tout abonnement, la fonction ne s'était exécutée aucune fois ; après le premier abonnement, une fois ; après le second, deux fois. Les flux HTTP sont paresseux, donc une barrière est franchie une fois par passage effectif du coursier, et non une fois pour toute l'application.

Le jeton périmé, ou l'erreur traitée en un seul endroit

Puisqu'il est permis d'ajouter des opérateurs sur le chemin du retour, tu peux y déposer aussi la gestion d'une erreur - et il existe une erreur qui se présente de la même façon dans chaque requête de toute l'application. Le statut 401 signifie "ton jeton n'est plus valable". Quoi que tu fusses en train de faire, la fin est la même : efface le jeton et renvoie l'utilisateur vers l'écran de connexion.

Toutes les pièces te sont déjà connues. catchError attrape l'erreur dans le flux, throwError la relance plus loin, HttpErrorResponse décrit une réponse en erreur - c'est la leçon sur les boucliers. Router et son navigate() viennent du module sur les sentiers. La seule nouveauté ici est une règle, celle qui concerne inject(), et tu vas voir tout de suite pourquoi elle compte autant.

1// interceptors/error.interceptor.ts
2import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
3import { inject } from '@angular/core';
4import { Router } from '@angular/router';
5import { catchError, throwError } from 'rxjs';
6import { AuthService } from '../services/auth.service';
7
8export const errorInterceptor: HttpInterceptorFn = (req, next) => {
9  const authService = inject(AuthService);
10  const router = inject(Router);
11
12  return next(req).pipe(
13    catchError((error: HttpErrorResponse) => {
14      if (error.status === 401) {
15        authService.logout();
16        router.navigate(['/login']);
17      }
18      return throwError(() => error);
19    })
20  );
21};

J'ai fait tourner ceci contre un serveur répondant par un statut 401. Résultat : le jeton a été effacé, le router a reçu l'ordre de passer sur /login, et le composant a malgré tout reçu l'erreur avec son statut 401. Ce dernier point est le plus essentiel ici et il découle du throwError final : la barrière n'avale pas l'erreur, elle la relâche plus loin, si bien que la gestion écrite plus tôt dans le service ne cesse pas de fonctionner un seul instant. Si tu renvoyais à la place une réponse inventée de ton cru, le composant n'apprendrait jamais que quelque chose a mal tourné.

Regarde bien l'endroit où se tiennent les deux inject() : tout en haut du corps de la barrière, avant pipe(). La tentation est grande de les écrire seulement à l'intérieur de catchError, là où ils sont réellement utiles. J'ai vérifié ce qui se passe alors : le code compile sans la moindre plainte, mais à la première erreur 401 l'application éclate avec le message NG0203, qui dit qu'inject() doit être appelée dans un contexte d'injection. La raison est simple : ce contexte n'existe qu'au moment où Angular appelle ta barrière, et le callback de catchError se déclenche bien plus tard, quand l'erreur revient du réseau - le contexte a disparu depuis longtemps. Les constantes obtenues en haut, elles, survivent sans peine jusqu'à cet instant, et c'est bien tout l'intérêt.

Le retry de la leçon sur les boucliers a ici une place aussi bonne que catchError, puisqu'il travaille sur le même flux issu de next(req). Nous ne le répéterons pas - la façon de faire est la même, la seule différence étant qu'écrit une seule fois, il couvre désormais toutes les requêtes.

L'ordre des barrières sur la route

Les barrières d'une même route se tiennent l'une derrière l'autre, et l'ordre d'inscription dans le tableau est l'ordre dans lequel on les franchit. Déclarons les trois d'un seul coup.

1// app.config.ts
2import { ApplicationConfig } from '@angular/core';
3import { provideHttpClient, withInterceptors } from '@angular/common/http';
4import { authInterceptor } from './interceptors/auth.interceptor';
5import { loggingInterceptor } from './interceptors/logging.interceptor';
6import { errorInterceptor } from './interceptors/error.interceptor';
7
8export const appConfig: ApplicationConfig = {
9  providers: [
10    provideHttpClient(
11      withInterceptors([authInterceptor, loggingInterceptor, errorInterceptor])
12    )
13  ]
14};

J'ai suivi le passage à travers ces trois postes et le résultat mérite d'être retenu : à l'aller, le coursier les croise dans l'ordre d'écriture, c'est-à-dire auth, logging, error, et ce n'est qu'ensuite qu'il sort sur le réseau. Au retour, il revient dans l'ordre inverse : error, logging, auth. C'est naturel, car chaque barrière emballe le flux de la précédente - plus on avance dans le tableau, plus on est près du réseau.

La conclusion pratique est que la barrière qui appose le sceau doit se tenir avant celle qui regarde le résultat. Le compteur de temps mesure alors le passage d'une requête déjà scellée, et la gestion des erreurs voit la réponse de la requête qui est vraiment sortie des murailles. L'ordre inverse compile aussi et fonctionnera même, mais tu commenceras à voir dans ton registre des requêtes sans l'en-tête que tu avais pourtant collé.

La barrière qu'il est permis de contourner

Il existe pourtant une route sur laquelle le sceau encombre. La devise inscrite au-dessus de la porte, chacun peut la lire sans jeton, et envoyer le token là où il n'est pas nécessaire, c'est au mieux du bavardage inutile, au pire montrer un secret du clan à un serveur étranger.

Il est tentant de vérifier l'adresse dans la barrière, quelque chose comme "si l'adresse se termine par motto, laisse passer sans sceau". Je te le déconseille : une condition fondée sur le texte de l'adresse casse au premier changement de chemin, et plus personne ne s'en souvient. La meilleure manière consiste à faire en sorte que ce soit l'appelant qui joigne à la requête un mot pour la barrière.

C'est à cela que sert HttpContext : un petit baluchon accroché à la requête et qui - c'est le point capital - ne part jamais sur le réseau. Il n'existe que dans le navigateur et il est là pour que ton code puisse parler à tes interceptors. Les clés de ce baluchon se créent avec la classe HttpContextToken, à qui l'on donne une fonction renvoyant la valeur par défaut.

1// interceptors/auth.context.ts
2import { HttpContextToken } from '@angular/common/http';
3
4export const SKIP_AUTH = new HttpContextToken<boolean>(() => false);

Les chevrons disent que sous cette clé se cache une valeur logique, et la fonction () => false donne la réponse à la question "et si personne n'a rien écrit". J'ai vérifié les deux chemins sur un objet vivant : un contexte fraîchement créé renvoie false pour cette clé, et une requête dont on n'a pas touché le contexte renvoie false elle aussi. Par défaut, donc, personne ne contourne la barrière, et c'est exactement ce qu'il faut - le contournement, il faut le demander explicitement.

Maintenant, la barrière doit lire ce mot. Chaque requête possède un champ context, dont tu extrais la valeur avec la méthode get(), en lui passant la clé.

1// interceptors/auth.interceptor.ts
2import { HttpInterceptorFn } from '@angular/common/http';
3import { inject } from '@angular/core';
4import { AuthService } from '../services/auth.service';
5import { SKIP_AUTH } from './auth.context';
6
7export const authInterceptor: HttpInterceptorFn = (req, next) => {
8  if (req.context.get(SKIP_AUTH)) {
9    return next(req);
10  }
11
12  const token = inject(AuthService).getToken();
13  if (!token) {
14    return next(req);
15  }
16
17  return next(req.clone({
18    headers: req.headers.set('Authorization', `Bearer ${token}`)
19  }));
20};

La seule nouveauté ici, c'est la première paire de lignes - le reste est le même sceau qu'auparavant, seulement écrit de façon que chaque cas se termine par son propre return au lieu de se cacher dans un if imbriqué. La lecture du contexte se tient avant inject(), et c'est volontaire : puisque la requête doit partir sans sceau, inutile de demander son jeton au service.

Reste le côté appelant, c'est-à-dire ce coursier unique qui doit contourner la barrière. Le mot, tu l'accroches dans le même objet d'options où tu passais les en-têtes et responseType - c'est tout simplement un champ de plus.

1import { HttpClient, HttpContext } from '@angular/common/http';
2import { inject } from '@angular/core';
3import { SKIP_AUTH } from '../interceptors/auth.context';
4
5export class MottoService {
6  private http = inject(HttpClient);
7
8  readMotto() {
9    return this.http.get('/api/motto', {
10      responseType: 'text',
11      context: new HttpContext().set(SKIP_AUTH, true)
12    });
13  }
14}

J'ai fait passer deux requêtes à travers cette configuration. L'ordinaire, celle qui part chercher la liste des samouraïs, est arrivée au serveur avec l'en-tête Authorization. Celle qui part chercher la devise est arrivée sans lui - la barrière a lu le mot et a laissé filer le coursier sans sceau. Et voici ce qui n'a pas changé au passage : pour le serveur, les deux requêtes ont l'air tout à fait ordinaires, car le baluchon avec le mot n'est parti nulle part, et la copie de la requête faite dans la barrière transporte le contexte plus loin avec le reste des champs.

Il y a ici une différence avec les en-têtes sur laquelle on trébuche facilement. HttpHeaders était immuable et son set() renvoyait un nouvel objet. HttpContext se comporte à l'inverse : j'ai vérifié que son set() modifie l'objet sur place et renvoie ce même objet. La chaîne new HttpContext().set(...) fonctionne donc dans les deux cas, mais pour deux raisons différentes, et ce n'est qu'avec les en-têtes que perdre le résultat te coûte la perte de la modification.

Retiens ceci : une barrière, c'est un unique endroit sur la route par lequel passe chaque coursier - inscris-y le sceau, et tu cesseras de l'inscrire partout ailleurs.

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. À quoi servent les interceptors HTTP ?

  2. 2. Quel type utilise-t-on pour marquer un interceptor fonctionnel moderne dans Angular 19 ?

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Complète l'interceptor qui ajoute un token d'autorisation à chaque requête.

  • Tri horizontal

    Ordonne, sur une seule ligne, les éléments qui enregistrent un interceptor dans provideHttpClient :

  • Tri vertical

    Ordonne les lignes consécutives à l'intérieur d'un interceptor qui ajoute un token :

  • Clique dans l'ordre

    Construis l'appel qui renvoie la requête modifiée depuis l'interceptor via next() :

Articles utiles