Préambule
- Les démonstrations de ce TD se basent sur les sources disponibles [ ici ].
- Attention, seul le répertoire src est dans l'archive. Il faut donc créer un projet avec vue-cli et remplacer son répertoire src.
1°/ Que sont les slots ?
- Le principe des slots est relié à celui des props : il sert à passer des choses d'un composant parent à un fils.
- Les props permettent de passer des valeurs,
- Les slots permettent de passer du HTML+directives vuejs.
- Le slots sont donc un moyen pour un composant parent de modifier l'apparence visuel d'un composant fils.
- Encore faut-il que le fils soit implémenté pour autoriser cela, ce qui se fait grâce à des directives spéciale de vuejs.
2°/ Principes de création
Dans le composant fils :
- Pour créer des slots dans un composant, on utilise la balise <slot> dans la partie template du composant.
- Si on met quelque chose entre l'ouverture et la fermeture de <slot>, cela constitue le contenu par défaut au cas où le composant parent de donne pas de contenu.
- Ce contenu par défaut peut bien entendu être défini grâce aux variables et aux props du composant fils.
- Cette balise a un attribut name qui permet de donner un nom aux slots et ainsi différencier leur contenu.
- On peut également créer une (et une seule) balise slot sans attribut name. C'est la balise par défaut. Son nom existe quand même : c'est default.
- Remarque 1 : si un slot avec le même nom apparaît plusieurs fois dans le template du composant, le contenu donné par le parent sera copié/collé autant de fois (cf. exemple basique ci-dessous).
- Remarque 2 : dans le cas de la remarque 1, on peut donner un contenu par défaut différent à chaque exemplaire du slot. En revanche, si le composant parent donne du contenu, ce sera le même pour tous les exemplaires.
Dans le composant parent :
- Dans le composant parent, pour affecter quelque chose à un slot particulier, on utilise : <template v-slot:"nom_slot"> contenu </template>
- S'il existe un slot par défaut, on peut simplement mettre le contenu, sans l'entourer par avec <template>.
Démonstration :
- Renommer App1.vue dans App.vue.
- lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
- Montrer le code de TD7Demo1.vue. On observe que le composant définit 4 slots, mais il y en a 2 qui ont le même nom, à savoir banner.
- On remarque également que ces 2 slots ont un contenu par défaut différent, l'un étant créé à partir d'une variable de data.
- Montrer le code de App.vue. On observe que le contenu du slot par défaut de TD7Demo1 est définit sans utiliser <template v-slot:"default">.
- Le contenu du slot banner n'est pas défini (en commentaire), et celui du slot title contient un titre dont la valeur vient de la variable title.
- Vu que App ne définit pas le contenu de banner, ce sont les contenus par défaut qui sont affichés.
- Décommenter la définition de banner => on voit apparaître 2 fois le contenu défini dans App.
Question : vu qu'il existe une variable title aussi bien dans le père que le fils, laquelle va être utilisée pour remplir le slot ?
- D'après ce qui s'affiche, on voit que c'est celle de App qui est utilisée.
- Normal car ce qui est virtuellement copié/collé dans le slot du composant fils n'est pas ce que le programmeur a écrit en HTML+vuejs dans App, mais le résultat de son évaluation.
- En fait, {{ title }} est écrit dans App, donc évalué dans App, ce qui utilise la variable title de App. C'est le résultat de cette évaluation est utilisé dans le slot de TD7Demo1.
Remarques :
- Dans App.vue, on peut mettre les template de slot dans n'importe quel ordre.
- On remarque que le contenu qui n'est pas mis entre <template> est bien copié/collé dans le slot par défaut.
3.2°/ Elargir la "portée" des slots : les scoped-slots
3.2.1°/ Principes et utilité
- Dans la démonstration précédente, le contenu du slot title est défini dans App en utilisant la variable title de App.
- Est-il possible de définir ce contenu grâce à la variable title de TD7Demo1 ? A priori, non.
- En effet, le composant père n'a pas accès directement aux données du composant fils.
- Pourtant, cela serait bien pratique pour les slots.
- En effet, prenons un composant A qui contient un composant B. Ce dernier affiche une liste à partir de ses données propres.
- De base, la façon dont s'affiche cette liste est figée par le template du composant B.
- Comment A peut-il influer sur cet affichage ?
- Une solution possible est de prévoir des props dans B, que A va pouvoir fixer et qui vont influer sur l'affichage.
- Mais ce n'est pas une solution universelle car même si on peut ainsi changer le style voire choisir un ensemble de balises à utiliser pour créer la liste, on ne peut pas prévoir toute les façon d'afficher cette liste (à puce, en tableau, en ligne, ...)
- La solution universelle est apportée par une catégorie spéciale de slot, appelée les scoped-slots.
- Un scoped-slot est un slot pour lequel on définit un certain nombre de variables et/ou fonctions du composant fils auxquelles le père va avoir accès pour définir le contenu du slot (et pas un autre).
- Dans l'exemple du composant B, on voudrait par exemple fournir à A chaque item de la liste. Comme ça, A décide totalement du rendu de chaque item.
- Par exemple, si chaque item est en fait un objet contenant plusieurs champs, le composant A pourrait décider lesquels s'affichent.
- Il pourrait aussi décider de ne pas afficher certains items, ou encore d'afficher des les items avec des styles différents, ...
3.2.2°/ Création
- Un scoped-slot ne rend accessible (= publie) à son composant parent que des variables de data ou bien une fonction définie dans methods.
- NB : il n'y a aucun intérêt publier une props (déjà accessible dans le composant parent) ou une valeur fixe.
- Pour publier, on utilise dans la balise <slot> des instructions v-bind:nom_publication="nom_variable/fonction".
- Cette syntaxe permet de mettre un nom de publication différent de celui de la variable/fonction. Mais généralement, on prend le même nom. Exemples des 2 cas :
- <slot name="myslot1" :list="items"></slot> : le composant fils rend accessible sa variable items au composant père, sous le nom list.
- <slot name="myslot2" :shops="shops"></slot> : le composant fils rend accessible sa variable shops au composant père, avec le même nom shops.
- Le composant fils peut publier plusieurs variables/fonctions. Pour cela, on utilise autant de fois v-bind qu'il y a de variables à publier. Par exemple :
- <slot name="myslot" :messages="messages" :selection:"selected"></slot>
- Toutes les variables publiées vont être regroupées dans un unique objet qui va être accessible au composant père.
- Généralement, on ne récupère pas cette objet dans le composant parent, mais plutôt directement les champs de cet objet, donc les variables qui ont été publiées par le composant fils.
- Pour faire cela, on utilise l'opérateur de décomposition d'objet de JS : { champ1, champ2, ...}
Exemple :
let obj = {txt:"hello", val: 4}
let {txt, val} = obj
console.log(txt+" "+val)
- Dans cet exemple, on décompose les champs d'un objet obj et on les affecte à des variables de même nom, donc txt et val.
- C'est ce principe que l'on utilise dans le composant père pour accéder à tout ou une partie de ce qui a été publié par un slot, avec la syntaxe <template v-slot:nom_slot="{nom1, nom2, ...}">
- Le composant père peut ensuite utiliser directement nom1, nom2, ... pour définir le contenu du slot.
Démonstration :
- Renommer App2.vue dans App.vue.
- lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
- Montrer le code de TD7Demo2.vue. On observe que le composant définit 1 seul slot nommé list-item.
- Comme le slot permet de définir le contenu de <li> et que cette balise est répétée, le slot va être également répété.
- Cela permet pour chaque occurence de publier la même variable item, mais avec une valeur différente.
- On remarque également qu'il est parfaitement possible de publier une fonction.
- Problème : dans l'exemple précédent, on a vu que plusieurs exemplaires du même slot sont remplis avec le même contenu.
- Mais grâce au principe des scoped-slot, le composant père va recevoir une valeur différente pour item.
- En récupérant cette variable item, le père peut customiser différemment chaque exemplaire du slot !
4°/ combinaison props et slots
- Dans la démonstration précédente, on constate que le tableau des items est défini dans le composant fils.
- D'un point de vue conception, ce n'est pas réaliste. Il est quasi certain que cette liste devrait être définie chez le père, notamment parce que d'autres composants fils pourraient en avoir besoin.
- Pour régler le problème, il suffit que le père donne cette liste en props à ses fils.
- Mais si on utilise en plus les scoped-slots, on arrive à une situation "bizarre" :
- le père donne la liste en props au fils,
- le fils utilise cette props pour créer un scoped-slot par item, afin de passer chaque item au père.
- le père récupère chaque item publié et l'utilise pour définir le contenu du slot correspondant.
- Cela semble inutile que le fils donne accès aux items au père puisque ce dernier y a déjà accès.
- Cependant, cet accès ne permet pas une manipulation comme si c'était une variable de data. Cela ne sert que dans la partie <template> et c'est seulement pour définir le contenu du slot.
- En conclusion, même si ce semble un peu compliqué, la combinaison props+scoped-slot est un moyen très puissant pour fournir des données ET l'apparence de celles-ci à un composant fils.
- Par exemple, ce principe est très utilisé dans la bibliothèque de composants graphiques vuetify.
Démonstration :
- Renommer App3.vue dans App.vue.
- lancer npm run serve puis visualiser le résultat dans le navigateur (par ex. http://localhost:8080)
- Un clic sur "buy" fait disparaître l'objet de TD7Demo3A et il apparaît dans TD7Demo3B.
- C'est l'inverse quand on clique sur "sell".
- Dans chaque cas, le montant d'or est actualisé.