Le puissant moteur de mise en correspondance d’URL du routeur Angular

Philippe Martin
Angular
Published in
7 min readOct 8, 2016

--

Traduction de l’article The Powerful URL Matching Engine of Angular Router de Victor Savkin.

Au cœur du routeur Angular repose un puissant moteur de mise en correspondance d’URL, qui transforme les URLs et les convertit en états de routeur. Il est important de comprendre le fonctionnement de ce moteur pour implémenter des cas complexes.

Cet article est basé sur le livre « Angular 2 Router » que vous pouvez trouver ici https://leanpub.com/router. Le livre va au-delà d’un simple guide de démarrage et décrit le routeur en profondeur. Le modèle mental, les contraintes de design et les subtilités de l’API — tout est couvert. Si vous aimez l’article, consultez le livre !

Commençons avec cette configuration.

Tout d’abord, notez que chaque route est définie par deux éléments clés :

  • sa correspondance avec l’URL.
  • Ce qu’elle fait une fois que l’URL est mise en correspondance.

Il est important que la seconde préoccupation, l’action, n’affecte pas la mise en correspondance.

Et disons que nous naviguons vers “/inbox/33/messages/44”.

Voici comment la mise en correspondance fonctionne :

Le routeur traverse les routes du tableau, une par une, et vérifie si la partie non consommée de l’URL commence par le chemin d’une de ces routes.

Ici il vérifie si “/inbox/33/messages/44” commence par “:folder”. C’est le cas. Le routeur définit alors le paramètre folder à “inbox”, puis prend les descendants de la route correspondante, la suite de l’URL, qui est “33/messages/44”, et continue la mise en correspondance.

La routeur va vérifier si “33/messages/44” commence avec “”, ce qui est le cas, puisque nous considérons que toutes les chaînes commencent avec la chaîne vide. Malheureusement, cette route n’a aucune route enfant et nous n’avons pas consommé l’URL entière. Le routeur va alors faire marche arrière pour essayer la route suivante “path: ‘:id’”.

Celle-ci va marcher. Le paramètre id sera défini à ‘33’, et pour finir la route “messages/:id” sera mise en correspondance, et le second paramètre id sera défini à ‘44’.

Marche arrière

Illustrons la marche arrière une fois de plus. Si le chemin pris à travers la configuration ne “consomme” par l’URL entière, le routeur fait marche arrière pour essayer un chemin alternatif.

Prenons cette configuration :

En naviguant vers “/a/c”, le routeur va commencer avec la première route. L’URL “/a/c” commence avec “path: ‘a’”, le routeur va alors essayer de faire correspondre “/c” avec “b”. Puisqu’il en est incapable, il va faire marche arrière et faire correspondre “/a/c” avec “:folder”, puis “c” avec “c”.

Parcours en profondeur d’abord

Le routeur n’essaie pas de trouver la meilleure correspondance, c’est-à-dire qu’il n’a aucune notion de spécificité. Il est satisfait avec la première correspondance qui consomme l’URL entière.

En naviguant vers “/a/b”, la première route sera mise en correspondance bien que la seconde semble plus ‘spécifique’.

Jokers

Nous avons vu que des expressions de chemin peuvent contenir deux types de segments :

  • segments constants (par exemple, path: ‘messages’)
  • segments variables (par exemple, path: ‘:folder’)

En utilisant uniquement ces deux nous pouvons couvrir la plupart des cas. Parfois, cependant, ce que nous voulons est la route “à défaut d’autre chose”. La route qui va correspondre à toute URL fournie. C’est ce que sont les routes joker. Dans l’exemple ci-dessous nous avons une route joker “{ path: ‘**’, redirectTo: ‘/notfound’ }” qui correspondra à toute URL que nous n’avons pas pu faire correspondre auparavant et qui activera “NotFoundCmp”.

La route joker va “consommer” tous les segments de l’URL, ainsi NotFoundCmp peut y accéder via l’`ActivatedRoute` injectée.

Routes de chemin vide

Si vous regardez une nouvelle fois notre configuration, vous verrez que certaines routes ont un chemin défini à une chaîne vide. Qu’est-ce que cela signifie ?

En définissant ‘path’ avec une chaîne vide, nous créons une route qui instancie un composant mais ne “consomme” aucun segment d’URL. Ceci signifie que si nous naviguons vers “/inbox”, le routeur va faire les choses suivantes :

Tout d’abord, il va vérifier si “/inbox” commence avec “:folder”, ce qui est le cas. Il va alors prendre le reste de l’URL, qui est “”, et les descendants de la route. Ensuite, il va vérifier si “” commence avec “”, ce qui est le cas ! Le résultat de tout ça est donc l’état de routeur suivant :

Des routes de chemin vide peuvent avoir des descendants et, en général, se comportent comme des routes normales. La seule différence pour ces routes est qu’elles héritent des paramètres matriciels de leurs parents. Ceci signifie que le résultat de cette URL “/inbox;expand=true” sera un état de route où deux routes activées auront le paramètre expand défini à true.

Stratégies de mise en correspondance

Par défaut le routeur vérifie si l’URL commence avec la propriété path d’une route, c’est-à-dire qu’elle vérifie si l’URL est préfixée par le chemin. Ceci est un comportement par défaut implicite, mais nous pouvons demander cette stratégie explicitement, de cette manière :

Le routeur supporte une seconde stratégie de mise en correspondance — full, qui vérifie si le chemin est “égal” à ce qu’il reste de l’URL. Ceci est particulièrement important pour les redirections. Pour en comprendre la raison, regardons cet exemple :

Puisque la stratégie par défaut est ‘prefix’ et que toute URL commence avec une chaîne vide, le routeur va toujours mettre en correspondance la première route. Même si nous naviguons vers “/inbox”, le routeur va appliquer la première redirection. Notre intention, néanmoins, est de faire correspondre la seconde route en naviguant vers “/inbox”, et rediriger vers “/inbox” en naviguant vers “/”. Maintenant, si nous changeons la stratégie de mise en correspondance à ‘full’, le routeur va appliquer la redirection uniquement lorsque nous naviguons vers “/”.

Routes sans composant

La plupart des routes dans la configuration ont soit la propriété redirectTo, soit la propriété component définie, mais certaines n’ont aucune des deux. Par exemple, regardez la route “path: ‘:folder’” dans la configuration ci-dessous.

Nous appelons de telles routes des routes ‘sans composant’. Leur objectif principal est de consommer des segments d’URL, fournir des données à leurs descendants, et ceci sans instancier aucun composant.

Les paramètres capturés par une route sans composant seront fusionnés avec les paramètres de ses descendants. Les données résolues par une route sans composant seront aussi fusionnées. Dans cet exemple, les deux routes descendantes auront le paramètre folder dans leurs paramètres.

Cet exemple précis aurait pu être simplement réécrit de cette manière :

Nous avons à dupliquer le paramètre “:folder”, mais globalement ça fonctionne. Quelquefois, cependant, il n’y a pas d’autre option que d’utiliser une route sans composant.

Composants de même niveau utilisant les mêmes données

Par exemple, il est utile de partager des données entre composants de même niveau.

Dans l’exemple suivant nous avons deux composants — MessageListCmp et MessageDetailsCmp —que nous voulons placer l’un à côté de l’autre, et tous deux ont besoin du paramètre id du message. MessageListCmp utilise l’id pour mettre en surbrillance le message sélectionné et MessageDetailsCmp l’utilise pour afficher les informations sur le message.

Une façon de modéliser cela serait de créer un faux composant parent, auprès duquel MessageListCmp et MessageDetailsCmp obtiendraient le paramètre id ; nous pourrions modéliser cette solution avec la configuration suivante :

Avec cette configuration mise en place, naviguer vers “/messages/11” donnerait l’arbre de composants :

Cette solution a un problème—nous devons créer le faux composant, qui n’a pas de d’utilité réelle. Dans ce cas l’utilisation d’une route sans composant est une bonne solution :

Maintenant, en naviguant vers “/messages/11”, le routeur va créer l’arbre de composants suivant :

Combiner des routes sans composant et de chemin vide

Ce qui est vraiment intéressant avec ces fonctionnalités est qu’elles se combinent de manière harmonieuse. Nous pouvons ainsi les utiliser ensemble pour résoudre des cas complexes en quelques lignes de code seulement.

Laissez-moi vous donner un exemple. Nous avons appris que nous pouvons utiliser des routes de chemin vide pour instancier des composants sans consommer de segment d’URL, et nous pouvons utiliser des routes sans composant pour consommer des segments d’URL sans instancier de composant. Qu’en est-il si nous les combinons ?

Ici nous avons défini une route qui ni ne consomme de segment d’URL ni ne crée de composant, mais est seulement utilisé pour exécuter des gardes et récupérer des données qui seront utilisées aussi bien par MesssagesCmp que par ContactsCmp. Les dupliquer dans les descendants n’est pas une alternative car le garde comme le résolveur de données peuvent faire des opérations asynchrones coûteuses et nous voulons les exécuter une seule fois.

Pour résumer

Nous avons appris beaucoup de choses ! Nous avons d’abord parlé de la façon dont le routeur fait la mise en correspondance. Il traverse les routes fournies, une par une, en vérifiant si l’URL commence avec le chemin d’une route. Puis, nous avons appris que le routeur n’a aucune notion de spécificité. Il parcourt prioritairement en profondeur la configuration, et s’arrête après avoir trouvé le chemin mettant en correspondance l’URL entière, c’est-à-dire que l’ordre des routes dans la configuration a de l’importance. Ensuite, nous avons parlé de routes de chemin vide qui ne consomment pas de segment d’URL et de routes sans composant qui n’instancient aucun composant. Nous avons montré comment nous pouvons les utiliser pour résoudre des cas complexes.

Aller plus loin

Suivez @victorsavkin pour en savoir plus sur Angular et TypeScript.

--

--