L'archive des sources du premier exemple est téléchargeable [ ici ]

L'archive des source de l'exemple complet est téléchargeable [ ici ]


 1°/ Principes de vue-router

  • vue-router permet de construire une application SPA vuejs à la façon d'une API basée sur des routes pour déterminer quelles informations sont demandées.
  • vue-router permet donc de définir des routes, sous la forme d'un chemin dont certaines parties représentent des paramètres (i.e. /a/:myparam/c)
  • Chaque route est associée à un ou plusieurs composants et non pas des traitements.
  • Par conséquent, quand on suit une route, cela ne provoque pas un traitement (par ex. une requête dans une BdD), mais l'affichage des composants associés à la route.

ATTENTION ! Les emplacements d'affichage des composants est le même pour toutes les routes, ce qui constitue une contrainte forte pour structurer le visuel et la navigation dans l'application.

  • Généralement, on donne un nom à ces emplacements et quand on définit une route, on indique pour les composants associés le nom de l'emplacement où ils seront affichés.
  • Fort heureusement, s'il y a 4 emplacements prévus dans l'application, une route n'est pas obligée d'être associée à 4 composants pour remplir les 4 emplacements. Elle peut en laisser certains vides, ce qui donne de la souplesse pour concevoir le visuel de l'application.
  • utiliser vue-router consiste donc à:
    • dans un fichier JS spécial : définir les routes possibles, quels composants elles permettent d'afficher et dans quel emplacement,
    • dans les template des composants :
      • définir des emplacements d'affichage pour les composants associés à des routes,
      • créer des liens ou des actions dans les composants de l'application, qui vont permettre de suivre ces routes.

 

  • L'énorme avantage de cette approche est que l'on peut facilement ajouter des routes, et donc des composants à afficher, au fur et à mesure du développement.
  • Par exemple, une application SPA avec un menu sur la gauche et une page centrale devient triviale à programmer grâce à vue-router.
  • Il suffit que chaque item du menu permette de suivre une route différente et que la page centrale serve à afficher le composant associé à chaque route.
  • Plus besoin de faire des v-if, v-else-if, ... pour afficher tel ou tel composant !

 

2°/ Intégration dans un projet

  • La plus simple façon d'utiliser vue-router est de l'inclure au moment de la création d'un projet avec vue-cli.
  • Pour cela, il suffit d'utiliser la sélection manuelle de "features" dans le menu qu'affiche vue-cli.
  • Juste après, une liste de différent plugins est proposée et il suffit de cocher Router, puis appuyer sur entrée.
  • Ensuite, vue-cli pose des questions (version de vue, utilisation de babel/lint, ...), pour lesquelles la réponse par défaut est généralement la bonne.

ATTENTION ! Par défaut, les configuration de babel, eslint, ... vont être dispatchées dans différents fichiers. Par exemple, pour configurer eslint, il faut modifier .eslintrc.js

 

  • On créer ainsi un projet type "HelloWorld" mais où vue-router est intégré.
  • Par exemple, sans src, on constate l'apparition des répertoires views et router.
  • views contient les composants qui vont être associés à des routes. A noter qu'ils n'ont rien d'autre de particulier : on peut très bien les mettre dans n'importe quel répertoire, par exemple components. C'est juste dans un soucis de clarté qu'on les sépare des autres composants, non associés à des routes.
  • router contient un fichier JS qui définit les routes et crée le routeur de l'application (NB : il n'y a qu'un seul routeur par application)
  • ce fichier est importé dans main.js, ce qui permet à l'instance de vue d'accéder au routeur.
  • App.vue a également complètement changé : on découvre des balises <router-link> et <router-view> qui sont propres à vue-router et décrites ci-dessous.

 

3°/ Syntaxe basique

3.1°/ Définir une route

  • Pour créer une route dans un projet créé avec vue-cli, on modifie le fichier index.js se trouvant dans le répertoire router.
  • Une route est un objet JSON comportant au moins 2 propriétés :
    • path : le "chemin" décrivant la route
    • components : les noms des emplacements utilisés et le nom des composants qui s'y affichent.
  • NB : quand l'application est très simple et ne nécessite qu'un seul emplacement pour toutes les routes, on peut utiliser la propriété component (sans s), qui indique juste le nom du composant à afficher dans l'emplacement.

 

  • On peut utiliser une expression rationnelle pour la valeur de path. Par exemple, on peut utiliser * pour remplacer un certain nombre de caractères.
  • Cela permet, entre autre, de créer une route par défaut quand aucune autre ne correspond.

ATTENTION ! l'ordre dans lequel on définit les route est important. La première rencontrée dont le format correspond à celle que l'on veut suivre est prise.

 

  • Une route peut  également avoir un nom grâce à la propriété name.
  • Ce nom est ensuite utilisable pour suivre la route (plutôt qu'avec un chemin).

 

  • Exemple : 
{
    path: '/home',
    components: {
      main: Accueil,
      left : Menu
    },
    name: 'home'
}
{
    path: '*',
    components: {
      main: Error404
    }
}

Remarques :

  • on suppose qu'il y a deux emplacements d'affichage nommés main et left.
  • quand on suit la route /home, on affiche le composant Accueil dans l'emplacement main et le composant Menu dans l'emplacement left.
  • toute autre route suivie aboutira à afficher le composant Error404 dans l'emplacement main et left sera vide.

 

3.2°/ Suivre une route

  • Pour suivre une route, vue-router propose deux solutions :
    • créer des liens cliquables qui vont apparaître dans les composants : balise <router-link>
    • appeler une fonction qui prenant une route en paramètre : $router.push().
  • En fait <router-link> va permettre de créer une balise <a href="/..."> et quand on clique sur le lien, cela appelle $router.push().
  • Les deux solutions sont donc strictement équivalentes dans l'effet.

ATTENTION ! Il existe une troisième possibilité inévitable : l'utilisateur peut taper directement une route dans la barre de navigation. C'est une façon simple de mener des attaques contre l'application. Il faut donc coder ses routes (notamment celles avec paramètres) en tenant compte de cette possibilité.

3.2.1°/ <router-link>

  • Cette balise a un attribut to qui permet de spécifier la route à suivre.
  • On peut fixer la valeur de to avec une chaîne constante, ou bien avec un objet donnant le nom et éventuellement les paramètres de la route. Dans ce cas, on utilise v-bind pour assigner cet objet.
  • Exemples (en se basant sur les routes définies précédemment) :
<router-link to="/home">Home</router-link>
<router-link :to="{path: '/home'}">Home</router-link>
  • La première et la deuxième ligne ont un résultat identique.
  • La différence est qu'avec :to, on peut passer des valeurs de paramètres dynamique (abordé en 4.2)

 

3.2.2°/ $router.push()

  • On veut parfois suivre une route suite à un événement qui n'est pas un clic sur un lien.
  • Dans ce cas, on utilise la fonction push() qui prend en paramètre exactement la même chose que l'attribut to.
  • Exemple (on suppose qu'il est placé dans un composant ayant une variable/props user) :
<button @click="$router.push({ name: 'useredit', params: { id: user.id, edit_mode: 'reset'} })"> Reset profile of {{user.name}} </button>

 3.2.3°/ requête

  • Dans une URI, il est possible de spécifier des variables de requête, en utilisant après le chemin : ?var1=val1&var2=val2& ...
  • C'est également possible pour suivre une route vue-router.
  • Le composant qui sera affiché peut récupérer ces variables dans $route.query.
  • Exemple :
<router-link to="/home?msg=hello">
  •  Dans le template du composant affiché, {{ $route.query.msg }} permettra d'afficher hello.

 

3.3°/ Afficher les composants associés à une route.

  • Pour définir les emplacement où les composants associés aux routes vont s'afficher, on utilise la balise <router-view>.
  • Cette balise à un attribut name qui fixe le nom de l'emplacement, et qui est utilisé lorsque l'on définit les routes.
  • RAPPEL : les emplacements sont fixes dans l'application. C'est logique puisqu'ils sont définis par là où on met les balises <router-view>.

Bizarrerie : on peut utiliser plusieurs fois <router-view> avec le même attribut name. Cela permet simplement d'afficher plusieurs fois le même composant à plusieurs endroits. Quelle utilité ??

  • La contrainte de fixité est assouplie par le fait qu'une route n'est pas obligé d'afficher un composant pour tous les emplacements définis.
  • De plus, il est possible de créer des emplacements imbriqués, grâce à des routes à plusieurs niveaux (cf. section 4).
  • Enfin, si besoin, v-if permet de contourner la contrainte de fixité : il permet d'utiliser <router-view> à tel ou tel endroit dans un composant en fonction de conditions.
  • En conclusion, avec un peu de pratique, vue-router peut être utilisé avec n'importe quelle structuration graphique d'application.

 

3.4°/ Exemple illustratif

  • Soit un site pédagogique avec des bannières en haut et bas, un menu sur la gauche, et une page centrale. Le contenu de la page centrale et des bannières change en fonction des clics sur le menu.
  • Chaque item de menu correspond à une route différente.

 

  • L'archive des sources de l'exemple est téléchargeable [ ici ]

Démonstration :

  • Montrer le code de router/index.js. On constate qu'il y a 3 routes déifinies avec chacune certains noms d'emplacement définis. On remarque aussi que l'on importe tous les composants qui seront affichés dynamiquement en fonction de la route choisie.
  • Montrer le code de components/Menu.vue. On constate que les liens sont créés grâce à <router-link> et un v-bind sur son attribut to, afin de suivre une route différente.
  • Montrer le code de components/MainPage.vue. On constate que l'emplacement locCentral est utilisé dans une balise router-view. Cela permet d'afficher le composant qui sera assigné à cet emplacement.
  • Montrer le code des autres composants.
  • Cliquer sur les différents liens pour constater les changements.
  • Dans BannerUp, copier/coller la balise router-view et changer name pour locDown. On constate que le composant qui est assigné à locDown apparaît également dans la bannière haute. C'est la preuve que lorsque l'on peut utiliser le même nom d'emplacement dans plusieurs balises router-view.

Remarque :

  • S'il y avait 10 matières, il faudrait créer 10 composants centraux, 10 pour la bannière basse et éventuellement 10 de plus pour la bannière haute => pas très pratique.
  • D'où les principes avancés vus ci-dessous.

 

4°/ Principes avancés

4.1°/ routes multi-niveaux et emplacements imbriqués

  • Le principe même des composants est de les imbriquer.
  • C'est pourquoi vue-router permet de créer des emplacements imbriqués.
  • Par exemple, si un composant contient <router-view name="loc1">, cela permet d'afficher un composant à la place de la balise.
  • Supposons que ce composant contienne lui-même <router-view name="loc2">, on obtient bien des emplacements imbriqués.
  • Dans cette situation, pour afficher quelque chose dans loc2, il faut obligatoirement définir une route à 2 niveaux.
  • Pour créer une telle route, on utilise la propriété children lors de la définition de la route.
  • Cela permet d'étendre une route existante, en complétant son chemin par de nouveaux segments.
  • Exemple :
    • App.vue contient dans son template <router-view name="loc1">.
    • componentA.vue contient dans son template <router-view name="loc2">.
    • quand on suit la route /niveau1, on veut afficher componentA dans l'emplacement loc1.
    • quand on suit la route /niveau1/niveau2.1, on veut afficher componentA dans loc1 ET componentB dans loc2.
    • quand on suit la route /niveau1/niveau2.2, on veut afficher componentA dans loc1 ET componentC dans loc2.
  • Dans router/index.js, on met :
{
    path: '/niveau1',
    components: {
      loc1 : componentA
    },
    children: [
      {
        path: 'niveau2.1',
        components: {
          loc2 : componentB
        }
      },
      {
        path: 'niveau2.2',
        components: {
          loc2 : componentC
        }
      }
    ]
}

 

 Remarques :

  • Ce mécanisme est applicable à N niveaux. Il suffit de créer des children de children de etc.
  • Dans cet exemple, il y a deux routes possibles pour le niveau 2 mais elles commencent toutes les deux par /niveau1. Cela implique que c'est forcément componentA qui s'affiche en loc1 pour les deux routes. Autrement dit, il est impossible de changer le composant du niveau n-1 quand on définit le niveau n.
  • Généralement, les variables path de niveau > 1 ne commencent jamais par /. Sinon, on "écrase" les segments de niveau inférieur. Par exemple, si on écrit path: '/niveau2.1', alors pour suivre cette route de niveau 2, on utilise seulement /niveau2.1 au lieu de /niveau1/niveau2.1, ce qui peut être source de confusion (voire incorrect si le niveau 1 est un paramètre).

 

ATTENTION ! il est conseillé d'utiliser des noms d'emplacements différents quel que soit le niveau. En effet, on pourrait très bien utiliser loc1 à la place de loc2 pour le second niveau car vue-router ne mélange pas les emplacements de différents niveaux. Mais avec des noms identiques, c'est bien difficile de s'y retrouver

 

4.2°/ routes paramétrées

  • Comme pour REST, certains segments de la route peuvent correspondre à des paramètres que l'on peut utiliser dans le composant qui sera affiché.

4.2.1°/ Création

  • Pour spécifier qu'un segment correspond à un paramètre, il suffit que le segment soit un mot commençant par :
  • On peut mettre plusieurs paramètres dans une route.
  • Pour récupérer la valeur des paramètres dans le composant et s'en servir dans son template, on utilise l'objet $route.params (ou this.$route.params quand on se trouve dans une méthode du composant)
  • Exemple : dans index.js
{
    path: '/users/:id/edit/:edit_mode',
    component: UserEdit,
    name: 'useredit'
}
  • dans UserEdit.vue :
<template>
   <div>   edit user {{ $route.params.id}} in mode {{$route.params.edit_mode}}  </div>
</template>
...
  • dans un composant permettant de suivre la route useredit
<router-link to="/user/23/edit/reset">User profile reset</router-link>
<router-link :to="{ name:'useredit', params: { id:23, edit_mode:'reset'} }">User profile reset</router-link>

Remarques :

  • les deux lignes ci-dessus ont le même effet, mais grâce à :to, on pourrait spécifier un id qui n'est pas fixe, dont la valeur est tirée d'une des variables du composant.
  • utiliser les paramètres vue-router dans un composant rend ce composant dépendant de l'utilisation de vue-router. Cela peut poser problème pour des composants se voulant réutilisables. 

 4.2.2°/ Changements de valeurs des paramètres

  • Si la valeur des paramètres change, vue-router considère que c'est toujours la même route et ne va pas recharger les composants associés à la route.
  • Si le composant utilise des instructions vuejs manipulant les paramètres, ces instructions seront automatiquement réévaluées, ce qui produira sans doute un changement à l'écran.
  • Par exemple, pour le composant UserEdit.vue ci-dessus :
    • si on suit la route /user/23/edit/reset, le composant affiche : edit user 23 in mode reset.
    • si après on suit la route /user/12/edit/changename, l’affichage change automatiquement en : edit user 12 in mode changename.
  • Cela fonctionnerait aussi avec des instructions du type v-for, v-if, ... Par exemple, si on veut afficher un sous-composant différent en fonction de la valeur d'un paramètre :
<div v-if="$route.params.edit_mode=='reset'"><FormReset /></div>
<div v-else><FormEdit /></div>
  •  Cela fonctionne également avec les méthodes de type computed.

 

  • En revanche, puisque le composant n'est pas rechargé, toutes les fonctions liées à la mise en place du composant (mounted(), created(), ...) ne sont pas rappelées.
  • Or, il est fréquent de mettre du code dans ces fonctions pour aller chercher des données externes (par ex, via axios) nécessaires à l'affichage du composant.
  • Si la recherche de données externes doit être relancée si les paramètres changent, il faut mettre dans une méthode qui observe les changements de paramètres, c.a.d. une fonction de type watch.
  • Par exemple, on suppose qu'une route du type /user/:id existe, permettant d'afficher le composant suivant :
<template>
    {{user.name}} {{user.age}}
</template>
<script>
  export default {
    ...
    data: () => {
      return { user: {name:'', age:''} }
    },
    watch: {
      $route(to, from) {
        this.user = ... // chercher avec axios les infos de user en fonction de $route.params.id
      }
    }
  }
</script>
  •  La section watch contient des méthodes qui ont le même nom que les variables dont on veut observer les changements. Ces méthodes ont 2 paramètres, to et from donnant la valeur de la variable après et avant changement.
  • En l'occurrence, on veut observer les changements de $route. A noter, que les paramètres to et from ne sont pas utiles dans ce cas puisque le traitement se fait en fonction du paramètre id.

 

4.3°/ Passer des props aux composants

4.3.1°/ via les paramètres d'une route

  • Comme dit plus haut, un composant utilisant $route.params n'est pas vraiment réutilisable.
  • Fort heureusement, vue-router fournit un mécanisme permettant de transformer les paramètres en props.
  • On peut ainsi coder le composant de façon totalement indépendante de vue-router.
  • Pour utiliser la conversion automatique, il faut ajouter une propriété props aux routes, et ce pour chaque emplacement lié à une route.
  • En effet, chaque emplacement peut potentiellement afficher un composant. Il faut donc donner les règles de conversion pour chacun afin que les composants reçoivent les bonnes props.
  • Il existe 3 principes de conversion:
    • prendre les paramètres et les transformer en props du même nom.
    • ne pas utiliser les paramètres mais fournir au composant un objet contenant les props que l'on désire.
    • utiliser une fonction dont le paramètre est l'objet route courant et qui va créer l'objet props fournit au composant.
  • La première solution n'est pas très générique : quand on crée un composant, on fixe le nom de ses props indépendamment de toute autre chose. Il faut donc créer des routes avec des paramètres dont les noms correspondent aux props, ce qui peut être compliqué, surtout avec des routes multi-niveaux.
  • La deuxième solution est peu utilisée car si on crée des routes avec des paramètres, c'est généralement pour les utiliser dans les composants affichés.
  • La troisième solution regroupe les deux précédentes et permet d'utiliser les paramètres, de les renommer, de passer d'autres props, etc.

 

Exemple solution 1, basé sur les routes paramétrées de la section 4.2.

  • Le composant UserEdit.vue devient :
<template>
    <div>edit user {{id}} in mode {{edit_mode}}</div>
</template>
<script>
  export default {
    name: 'UserEdit',
    props: [ 'id','edit_mode' ]
  }
</script>
  •  router/index.js contient :
{
    path: '/users/:id/edit/:edit_mode',
    name: 'useredit',
    components: {
      locProfile : UserEdit
    },
    props: {
      locProfile : true
    }
}
  •  C'est le fait de mettre true comme valeur à locProfile dans props qui indique à vue-router qu'il faut transformer les paramètres id et edit_route en props du même nom pour le composant qui sera affiché dans locProfile.
  • Dans le cas présent, c'est UserEdit qui est affiché, ce qui est cohérent puisqu'il a comme props id et edit_mode.

Exemple solution 3.

  • Cette fois, on suppose que UserEdit.vue a déjà été écrit comme suivant :
<template>
    <div>
        <h3>{{title}}</h3>
        edit user {{userId}} in mode {{editMode}}
        new user name : {{newName}}
    </div>
</template>
<script>
  export default {
    name: 'UserEdit',
    props: [ 'title', 'userId','editMode','newName' ]
  }
</script>
  •  On suppose également que le format de la route reste le même que précédemment mais qu'une variable rename de query permet de spécifier le nouveau nom (exemple de route : /user/46/edit/chgname?rename=toto)
  • Cela donne pour router/index.js
{
    path: '/users/:id/edit/:edit_mode',
    name: 'useredit',
    components: {
      locProfile : UserEdit
    },
    props: {
      locProfile : route => { return {title:'User profile', userId: route.params.id, editMode: route.params.edit_mode, newName: route.query.rename } }
    }
}
  • Cette solution repose sur la définition d'une fonction fléchée prenant en paramètre une variable, nommée route dans cet exemple (NB: on peut prendre le nom que l'on veut).
  • Le retour de cette fonction est un objet qui sera fourni comme props au composant affiché, en l'occurence UserEdit.
  • La valeur de la variable route est fixée par vue-router et correspond à l'objet route courant. C'est donc la même valeur que celle de $route dans un composant.
  • On peut donc accéder aux paramètres et query de la route courante, grâce à route.params et route.query.
  • L'objet retourné peut ainsi contenir des propriétés correspondant aux props de UserEdit, avec des valeurs soit constantes (par ex, title), ou bien tirées de la route courante.

4.3.2°/ via <router-view>

  • Comme n'importe quelle balise créée via vuejs, on peut ajouter n'importe quel attribut à <router-view>. 
  • Cela implique que l'on peut ajouter un attribut correspondant à une props du composant qui va s'afficher à l'emplacement de <router-view>
  • Si cet emplacement est utilisé pour afficher différents composants, il suffit d'ajouter autant d'attributs que l'on veut, correspondant aux différentes props de ces composants, en leur donnant une valeur fixe, ou bien avec v-bind.
  • En effet, un composant peut très bien être instancié avec des attributs qui ne lui serviront pas : cela ne provoque pas d'erreur.
  • Remarque : cela fonctionne aussi avec l'attribut v-on
  • Exemple, basé sur l'exemple illustratif de la section 3.4 :
    • Dans MainPage.vue, une balise <router-view> permet d'afficher soit le composant Home, soit le composant Cours, en fonction de la route suivie.
    • Si on suppose que Home et Cours ont respectivement des props nommées news et matieres, on peut leur donner une valeur grâce à v-bind dans <router-view>
<template>
    <div>
        <router-view name="locCentral" :news="infos" :matieres="matieres"/>
    </div>
</template>
<script>
  export default {
    name: 'MainPage',
    data : () => {
      return {
        infos : "bonjour à tous",
        matieres : [ {nom: "Algo"}, {nom: "Java"}]
      }
    },
   ...
}
</script>

5°/ Exemple complet

  • Un exemple qui récapitule tous les concepts abordés est téléchargeable [ ici ].
  • L'archive contient uniquement le répertoire src.