Refactoriser une application Angular peut être une arme à double tranchant. D'une part, cela vous permet d'améliorer la maintenabilité et l'évolutivité de votre base de code. D'un autre côté, cela peut conduire à des itinéraires interrompus si vous n'avez pas pris les précautions nécessaires pour protéger vos fonctionnalités contre des modifications involontaires. L'écriture de tests approfondis ou la mise en œuvre d'un concept de typage solide pour les itinéraires peuvent aider à atténuer ce risque, mais ces approches peuvent prendre du temps et ne sont pas toujours réalisables. Dans cet article, nous explorerons une solution plus efficace qui détecte automatiquement les routes interrompues au moment de la compilation, sans nécessiter d'efforts de test manuels ni besoin d'écrire des annotations de type personnalisées. Nous démontrerons cette approche en implémentant un exemple d'application angulaire avec des composants imbriqués et en utilisant la bibliothèque typesafe-routes pour améliorer l'expérience des développeurs et faciliter l'analyse des paramètres.
Pour illustrer les avantages de la détection automatique des routes interrompues au moment de la compilation, nous allons implémenter un exemple d'application Angular avec trois composants imbriqués : DashboardComponent (/dashboard), OrgsComponent (/orgs/:orgId) et LocationsComponent (/orgs /:orgId/locations/:locationId). Pour configurer cet exemple, nous devrons installer la bibliothèque typesafe-routes et utiliser sa fonction createRoutes pour définir notre arborescence de routes, comme indiqué dans le fragment de code suivant.
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
Regardons de plus près le fragment de code. Nous importons createRoutes à partir de typesafe-routes et transmettons nos routes comme premier argument. Ces routes sont définies comme un objet imbriqué avec deux propriétés au niveau racine : tableau de bord et organisations. Chacune de ces propriétés se voit attribuer un chemin, spécifiant les segments sous la forme d'un tableau. Par exemple, le tableau ["dashboard"] correspond au chemin /dashboard. Le chemin orgs est plus complexe, car il contient un paramètre nommé orgId de type entier. Notez qu'entier n'est pas un type JavaScript natif, mais plutôt un type personnalisé défini à l'aide de la fonction int, qui imite les caractéristiques d'un entier en utilisant un nombre en arrière-plan. La route orgs a une propriété children, qui spécifie une route enfant appelée locations. La route des emplacements est similaire à la route des organisations, mais elle spécifie une page de paramètres de recherche facultative supplémentaire de type int.
createRoutes utilise les informations sur les routes pour créer un contexte enveloppé dans un objet Proxy. Vous n'avez pas besoin de connaître les détails de cet objet proxy, mais il est essentiel de comprendre que grâce à cet objet, vous pouvez accéder à toutes les spécifications de routes n'importe où dans votre application pour restituer et analyser les routes et les paramètres.
Nous avons attribué l'objet Proxy renvoyé par createRoutes à r. Cela signifie que vous pouvez accéder au chemin du tableau de bord avec r.dashboard, au chemin des emplacements avec r.orgs.locations, etc.
Une fois nos itinéraires définis, nous pouvons maintenant passer à l'étape suivante : les enregistrer auprès d'angular-router.
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
Le fragment de code montre une configuration commune avec des routes imbriquées pour Angular Router qui reflète l'arborescence de routes que nous avons définie précédemment. Cependant, au lieu d'utiliser des chaînes simples typiques pour spécifier les modèles de chemin (par exemple orgs/:orgId), nous importons la fonction de modèle depuis typesafe-routes/angular-router et l'utilisons pour générer les modèles de chemin. Pour DashboardComponent et OrgsComponent, nous pouvons simplement appeler le modèle avec leurs chemins correspondants r.dashboard et r.orgs pour obtenir les modèles. Cependant, le composant restant LocationsComponent est un enfant de OrgsComponent et nécessite donc un chemin relatif, qui ne peut pas être généré en utilisant r.orgs.locations car cela entraînerait un chemin absolu orgs/:orgId/locations/:locationId, alors qu'Angular Router attend un chemin relatif lors de l'imbrication des modèles d'itinéraire.
Pour générer un chemin relatif, nous pouvons utiliser le lien _, qui omet effectivement tout ce qui précède le caractère de soulignement. Dans ce cas, nous pouvons utiliser template(r.orgs._.locations) pour générer le chemin relatif. Il s'agit d'une fonctionnalité pratique, car elle nous permet de réutiliser le même arbre de routes dans des scénarios où nous devons restituer des chemins absolus mais également dans des situations qui nécessitent un chemin relatif.
À ce stade, nous avons déjà profité de la saisie semi-automatique et de la prévention des fautes de frappe dans notre IDE préféré (tel que Visual Studio Code). Et les changements futurs nous alerteront de toute faute d'orthographe ou faute de frappe dans nos chemins d'itinéraire, car tous les types peuvent être retracés jusqu'à la définition initiale des itinéraires avec createRoutes.
Maintenant que nous avons spécifié nos modèles d'itinéraire, nous souhaitons passer au rendu des liens. Pour cela, nous souhaitons créer un composant simple qui utilise des fonctions de rendu pour restituer ces liens, y compris la sérialisation de type et les vérifications de type. L'exemple suivant montre un composant qui affiche une liste d'éléments d'ancrage faisant référence à d'autres composants de notre application.
// app.routes.ts import { Routes } from "@angular/router"; import { template } from "typesafe-routes/angular-router"; export const routes: Routes = [ { path: template(r.dashboard), // ~> "dashboard" component: DashboardComponent, }, { path: template(r.orgs), // ~> "orgs/:orgId" component: OrgsComponent, children: [ { path: template(r.orgs._.locations), // ~> "locations/:locationId" component: LocationsComponent, }, ], }, ];
L'exemple de code importe render et renderPath depuis typesafe-routes/angular-router. renderPath restitue un chemin, tandis que render sérialise en outre les paramètres de requête pour notre liste de liens. Nous importons également r, l'objet proxy qui nous permet d'accéder aux informations sur les itinéraires précédemment définis et de définir l'itinéraire souhaité à rendre.
Tout d'abord, nous créons DashboardLink et orgsLink à l'aide de la fonction renderPath. Comme premier paramètre, il prend l'objet proxy susmentionné représentant le chemin de l'itinéraire à restituer. Le deuxième paramètre est un enregistrement avec des valeurs de paramètre correspondant au nom et au type du paramètre précédemment défini avec createRoutes dans app.routes.ts. La valeur de retour est une chaîne contenant le chemin appartenant au composant correspondant.
La fonction de rendu dans le troisième exemple restitue à la fois les paramètres de chemin et de recherche, et nécessite donc un chemin et une propriété de requête dans les définitions des paramètres. La valeur de retour ici est un objet avec les deux propriétés path et query. Nous définissons les deux propriétés comme valeurs des attributs [routerLink] et [queryParams].
L'analyse des paramètres est une partie essentielle des routes typesafe. Lors de la définition de l'itinéraire ci-dessus, nous avons défini quelques paramètres et leur avons donné un type int de type entier. Cependant, étant donné que les valeurs des paramètres proviennent de diverses sources telles que l'objet Location, elles sont basées sur des chaînes. Idéalement, typesafe-routes exporte des fonctions d'assistance qui analysent ces chaînes et les convertissent dans le type souhaité. L'analyse est basée sur notre objet proxy r que nous avons créé précédemment, ce qui signifie que nous devons indiquer à la bibliothèque à quelle route appartiennent les paramètres. L'exemple suivant le démontre en montrant deux scénarios d'analyse courants.
// app.routes.ts import { createRoutes, int } from "typesafe-routes"; export const r = createRoutes({ dashboard: { path: ["dashboard"], // ~> "/dashboard" }, orgs: { path: ["orgs", int("orgId")], // ~> "/orgs/:orgId" children: { locations: { path: ["locations", int("locationId")], // ~> "locations/:locationId" query: [int.optional("page")], // ~> "?page=[number]" }, }, }, });
Étant donné le location.href orgs/1/location/2?page=5, dans Angular, nous pouvons accéder aux paramètres de requête basés sur des chaînes en utilisant this.route.snapshot.queryParams et les paramètres de chemin basés sur des chaînes sont fournis via ceci. route.snapshot.params. En utilisant parseQuery avec r.orgs.locations et this.route.snapshot.queryParams, nous pouvons récupérer un objet avec le paramètre page sous forme de nombre. En utilisant parsePath avec r.orgs._.locations et this.route.snapshot.params, nous obtenons le locationId analysé. Dans ce cas, r.orgs._.locations est un chemin relatif et tous les segments avant le lien _ sont omis, ce qui fait que orgId n'est pas présent dans l'objet résultant.
Les fonctions d'analyse de typesafe-routes sont polyvalentes, et nous pouvons également extraire tous les paramètres directement de la chaîne location.href à la fois en utilisant parse.
// app.routes.ts import { Routes } from "@angular/router"; import { template } from "typesafe-routes/angular-router"; export const routes: Routes = [ { path: template(r.dashboard), // ~> "dashboard" component: DashboardComponent, }, { path: template(r.orgs), // ~> "orgs/:orgId" component: OrgsComponent, children: [ { path: template(r.orgs._.locations), // ~> "locations/:locationId" component: LocationsComponent, }, ], }, ];
L'extraction d'informations de type sur les paramètres est possible via InferQueryParams, InferPathParams ou InferParams. Voici une démonstration du type d'utilitaire InferQueryParams.
// app.component.ts import { render, renderPath } from "typesafe-routes/angular-router"; import { r } from "./app.routes"; @Component({ selector: "app-root", imports: [RouterOutlet, RouterLink], template: ` <h1>Absolute Links</h1> <ul> <li><a [routerLink]="dashboardLink">Dashboard</a></li> <li><a [routerLink]="orgsLink">Org</a></li> <li> <a [routerLink]="locationLink.path" [queryParams]="locationLink.query"> Location </a> </li> </ul> <router-outlet></router-outlet> `, }) export class AppComponent { dashboardLink = renderPath(r.dashboard, {}); // ~> dashboard orgsLink = renderPath(r.orgs, { orgId: 123 }); // ~> orgs/123 locationLink = render(r.orgs.locations, { path: { orgId: 321, locationId: 654 }, query: { page: 42 }, }); // ~> { path: "orgs/321/location/654", query: { page: "42" }} } // ...
Pour conclure ce tutoriel, nous avons créé un arbre de routes unique r qui est l'unique source de vérité pour nos routes. Sur cette base, nous avons rendu des modèles que nous avons utilisés pour enregistrer nos composants auprès d'Angular Router. Nous avons rendu les chemins avec des segments de chemin dynamiques et des paramètres de requête. Nous avons analysé les paramètres pour les convertir des valeurs de chaîne en leurs types correspondants. Nous avons tout fait de manière sécurisée sans écrire une seule définition de type. Nous avons établi une arborescence de routes robuste qui évite facilement les bugs tout en développant de nouvelles fonctionnalités et facilite en outre les futures refactorisations.
Cependant, typesafe-routes possède de nombreuses autres fonctionnalités, telles que de nombreux types de paramètres intégrés différents, une intégration facile de types de paramètres personnalisés, la manipulation de sous-chemins, la définition de chaînes de modèles personnalisées et bien d'autres. Malheureusement, nous ne pouvons pas tous les couvrir dans ce tutoriel, mais vous pouvez en savoir plus en visitant la documentation officielle.
Bien sûr, de nombreuses améliorations potentielles peuvent également être mises en œuvre dans les exemples présentés dans ce tutoriel. Par exemple, une directive personnalisée pour le rendu des liens qui prend une définition de chemin basée sur notre objet proxy, tel que r.orgs.locations. Un autre exemple est une fonction qui génère automatiquement un tableau Routes pour Angular Router, éliminant efficacement le code dupliqué et la nécessité de maintenir les routes synchronisées avec notre arborescence de routes créée avec createRoutes dans le tout premier bloc de code.
Cependant, ce ne sont là que quelques façons parmi tant d’autres de contribuer. Le moyen le plus courant est bien sûr de partager, de signaler des bugs ou d'ouvrir des PR dans notre référentiel GitHub. Si vous utilisez cette bibliothèque et pensez qu'elle améliore votre expérience de développement, vous pouvez également m'offrir un café. Nous avons également une chaîne Discord où vous pouvez laisser des commentaires ou poser des questions.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!