Commentaires
Dans cette partie, nous allons récupérer les données de l'API de Mediastack. Ce qui est intéressant avec vue-router, c'est que nous avons maintenant la possibilité de récupérer les données de l'API à différents moments :
- avant la navigation
- après la navigation

La solution "avant la navigation" parait intéressante. Elle permet de récupérer les données d'une API avant que le composant change. Cela signifie qu'une fois qu'on affiche le composant, il n'y a aucune latence. Mais ce qu'il ne faut pas oublier, c'est que entre le moment où on clique sur le lien qui nous mène à ce dit composant et son affichage, on peut avoir une latence. Et sans trop d'informations... Donc l'utilisateur clique incessamment car il pense que ça n'a pas chargé. Et il est difficile d'afficher un état de chargement (loading bar par exemple). Il y a donc des avantages et des inconvénients, cela dépendra de l'expérience utilisateur que tu veux instaurer.

Avant la navigation


Nous devons utiliser deux fonctions offertes par Vue, qui sont des hooks propres au router.

Arthur, l'apprenti développeurPourquoi 2 ?
Il faut penser aux deux cas d'utilisation : soit une personne clique sur un lien qui l'amène à cette page, soit une personne est déjà sur la page et il y un changement de route sans que cela n'implique un changement de composant (par exemple, passer de localhost/news/1 à localhost/news/2).
Ces deux hooks sont beforeRouteEnter et beforeRouterUpdate. La logique des fonctions est un peu différente pour chacun.
Commençons par beforeRouteEnter. Il faut bien comprendre que lorsque ce hook est déclenché, il n'est pas possible d'accéder aux data de notre composants (ni aux props, ni rien du tout) puisqu'il n'a toujours pas été "appelé" en soit. C'est bien pour cela que ça s'appelle "avant la navigation" :p.

Commençons simplement :
{"language":"text/html","content":"<template>\n <h2 class=\"text-blue-400 text-xl\">Toutes les news</h2>\n\n <div v-if=\"error\" class=\"error\">{{ error }}</div>\n\n <div class=\"grid gap-16 pt-12 grid-cols-3 gap-x-5 gap-y-12\">\n <MiniNews v-for=\"currentNews in news\" v-bind=\"currentNews\" :key=\"currentNews\" />\n </div>\n\n</template>\n\n<script>\n\timport MiniNews from './../components/MiniNews.vue'\n export default {\n components: { MiniNews },\n data() {\n return {\n news: null,\n error: null,\n }\n },\n beforeRouteEnter(to, from, next) {\n // On veut : récupérer les données de l'API puis les assigner aux data news et error\n },\n</script>","filename":"NewsAll.vue"}

Arthur, l'apprenti développeurAttends, mais je le connais pas ton composant MiniNews !
Ce n'est pas très important, ce n'est que pour le design. Mais bon, tiens :

{"language":"text/html","content":"<template>\n <div>\n <router-link :to=\"{name:'News.Show', params:{id: id}}\" custom v-slot=\"{ navigate, href }\">\n <a :href=\"href\" @click=\"navigate\" class=\"block mt-4\">\n <p class=\"text-xl font-semibold text-gray-900\">\n {{ title }}\n </p>\n <p class=\"mt-3 text-base text-gray-500\">\n {{ miniDescription }}\n </p>\n </a>\n </router-link>\n <div class=\"mt-6 flex items-center justify-center\">\n <p class=\"text-sm font-medium text-gray-900\">\n <b>Source</b> : {{ source }}<br />\n <b>Auteur</b> : <span v-if=\"author\">{{ author }}</span><span v-else>Pas d'auteur communiqué</span><br />\n <b>Date</b> : <time :datetime=\"published_at\">{{ formattedDate }}</time>\n </p>\n </div>\n </div>\n</template>\n\n<script>\n const MAX_DESCRIPTION = 155\n export default {\n props: ['id','url', 'title', 'author', 'source', 'published_at', 'description'],\n computed: {\n formattedDate() {\n const year = this.published_at.substr(0, 4);\n const month = this.published_at.substr(5, 2);\n const day = this.published_at.substr(8, 2)\n return `${day}/${month}/${year}`\n },\n miniDescription() {\n return this.description.substr(0, MAX_DESCRIPTION) + '...'\n }\n }\n }\n</script>","filename":"MiniNews.vue"}


Je me suis basé sur la documentation de MediaStack pour connaitre le nom de mes props et la format des dates etc. Rien d'incompréhensible là-dedans normalement !

Arthur, l'apprenti développeurOk, merci. Alors, beforeRouteEnter ?
Comme je disais, cette fonction n'a pas accès aux données de notre composant (bien qu'étant dans notre composant (c'est dur à comprendre, je sais)).
Du coup, on ne peut pas faire un fetch suivi d'un this.news = data par exemple... On va devoir passer par l'API offerte par vue. En effet, regardons de plus près les 3 paramètres auxquels on accès avec beforeRouteEnter :

  • to : indique d'où l'on veut aller. C'est un objet qui contient des données sur le composant (ex : fullPath: "/news", hash: "", query: {}, name: "News.All", path: "/news", …})

  • from : indique d'où l'on vient (ex : {fullPath: "/", path: "/", query: {}, hash: "", name: "Home", …})

  • next : c'est un callback que l'on peut appeler (et qui va nous sauver la vie). Et avec beforeRouteEnter (car oui, en fait, ces 3 petits paramètres, on va les retrouver par la suite à d'autres endroits héhé, pas que dans beforeRouteEnter, pour compliquer la chose :D), ce next a accès à l'instance du composant qu'on veut afficher après, c'est à dire le composant lié au paramètre "to". Tu arrives à suivre ?


Arthur, l'apprenti développeurOulala, ça commence à devenir compliqué...
Oui, je l'admets. Mais ne t'en fais pas, ce n'est pas tous les jours que tu as auras besoin d'utiliser beforeRouteEnter (mais c'est toujours classe dans une discussion entre deux développeurs :D).

Bon, revenons-en à notre code. Maintenant qu'on sait tout ça, on va pouvoir avancer. On va faire appel ce paramètre next() pour ensuite accéder à l'instance de NewsAll.vue (composant où on veut aller) et assigner les valeurs à nos data error et news.
Commençons par quelque chose d'assez simple : le fetch.

{"language":"text/html","content":"<template>\n <h2 class=\"text-blue-400 text-xl\">Toutes les news</h2>\n\n <div v-if=\"error\" class=\"error\">{{ error }}</div>\n\n <div class=\"grid gap-16 pt-12 grid-cols-3 gap-x-5 gap-y-12\">\n <MiniNews v-for=\"currentNews in news\" v-bind=\"currentNews\" :key=\"currentNews\" />\n </div>\n\n</template>\n<script>\n import MiniNews from './../components/MiniNews.vue'\n export default {\n components: { MiniNews },\n data() {\n return {\n news: null,\n error: null,\n }\n },\n beforeRouteEnter(to, from, next) {\n let error = null\n let news = []\n fetch(`http://api.mediastack.com/v1/news?access_key=${process.env.VUE_APP_MEDIASTACK_KEY}&countries=fr`)\n .then(data => data.json())\n .then(data => {\n news = data.data.sort((a, b) => {\n return a.published_at < b.published_at\n })\n // Ici on doit passer news et error à notre instance\n })\n .catch(err => {\n error = (JSON.parse(err)).message\n // Ici on doit passer error à notre instance\n })\n }\n }\n</script>","filename":"NewsAll.vue"}

Normalement, ça ça devrait aller. Maintenant, notre next(). Comme je le disais, next est un callback offert par l'API de Vue, qui, dans notre contexte, nous donne accès un paramètre qui est l'instance du composant sur lequel on doit aller.
On peut donc l'utiliser comme suit : next(instanceDeMonComposant => faireCeQueJeVeux)
Par "habitude", ce composant sera appelé "vm".
Voici donc ce que ça donne :
{"language":"text/html","content":"<template>\n <h2 class=\"text-blue-400 text-xl\">Toutes les news</h2>\n\n <div v-if=\"error\" class=\"error\">{{ error }}</div>\n\n <div class=\"grid gap-16 pt-12 grid-cols-3 gap-x-5 gap-y-12\">\n <MiniNews v-for=\"currentNews in news\" v-bind=\"currentNews\" :key=\"currentNews\" />\n </div>\n\n</template>\n<script>\n import MiniNews from './../components/MiniNews.vue'\n export default {\n components: { MiniNews },\n data() {\n return {\n news: null,\n error: null,\n }\n },\n beforeRouteEnter(to, from, next) {\n let error = null\n let news = []\n fetch(`http://api.mediastack.com/v1/news?access_key=${process.env.VUE_APP_MEDIASTACK_KEY}&countries=fr`)\n .then(data => data.json())\n .then(data => {\n news = data.data.sort((a, b) => {\n return a.published_at < b.published_at\n })\n // Ici on doit passer news et error à notre instance\n\t\t\t\t\tnext(vm => {vm.news = news; vm.error = null})\n })\n .catch(err => {\n error = (JSON.parse(err)).message\n // Ici on doit passer error à notre instance\n\t\t\t\t\tnext(vm => {vm.news = null; vm.error = error})\n })\n }\n }\n</script>","filename":"NewsAll.vue"}

Si tu es arrivé jusque là, bravo, tout marche, tu peux essayer ! Tu remarqueras alors la latence quand tu veux accéder à la page. Celle dont je t'avais parlé plus haut.

Arthur, l'apprenti développeurEt c'est aussi compliqué avec beforeRouteUpdate ? :'(
Non ! Car on accès à nos data vu que le composant est déjà instancié ! Et tu sais quoi ? Dans notre cas, on n'a pas besoin de beforeRouteUpdate. En effet, la route ne changera jamais sans qu'on change de composant (autrement dit, on n'a pas d'attributs pour cette route). On aurait un beforeRouteUpdate dans le cas de notre composant NewsShow. Si ça t'intéresse, sache que j'en parle à la toute fin du cours ;).

Après la navigation


Ici, beauucccoup plus simple (et plus ergonomique selon moi).
Il s'agit tout simplement... D'utiliser created() comme on le ferait d'habitude ;). On va quand même gérer les status de loading ou pas et afficher les erreurs s'il y en a.
On se retrouve dans le composant NewsAll.vue. Je gère l'état de loading, j'ai factorisé le code, mais normalement tu dois tout comprendre, il n'y a rien de particulier

{"language":"text/html","content":"<template>\n <h2 class=\"text-blue-400 text-xl\">Toutes les news</h2>\n <div v-if=\"loading\" class=\"loading\">Loading...</div>\n\n <div v-if=\"error\" class=\"error\">{{ error }}</div>\n\n <div class=\"grid gap-16 pt-12 grid-cols-3 gap-x-5 gap-y-12\">\n <MiniNews v-for=\"currentNews in news\" v-bind=\"currentNews\" :key=\"currentNews\"/>\n </div>\n\n</template>\n<script>\n import MiniNews from './../components/MiniNews.vue'\n export default {\n components:{MiniNews},\n data() {\n return {\n loading: false,\n news: null,\n error: null,\n }\n },\n created() {\n this.fetchData()\n },\n methods: {\n fetchData() {\n this.error = this.news = null\n this.loading = true\n fetch(`http://api.mediastack.com/v1/news?access_key=${process.env.VUE_APP_MEDIASTACK_KEY}&countries=fr`)\n .then(data => data.json())\n .then(data => {\n this.loading = false\n console.log(data)\n\n this.news = data.data.sort((a, b) => {\n return a.published_at < b.published_at\n })\n })\n .catch(error => {\n this.error = (JSON.parse(error)).message\n })\n },\n },\n }\n\n</script>","filename":"NewsAll.vue"}


Normalement, tout devrait être clair pour toi. Ce sont des choses que tu connais.



Cette partie fut rude mais intéressante, les parties suivantes seront plus simples ne t'en fais pas ;).

Arthur, l'apprenti développeurD'ailleurs, que voit-on dans la partie suivante ?
On va afficher un article en particulier et ses commentaires (factices). Par contre, tu l'auras peut être remarqué, avec notre API, les news n'ont pas d'id... On va donc devoir bidouiller un peu. Cela n'est pas grave, notre site n'a pas vocation à devenir la révélation de l'année !
J'ai terminé cette partie
Demander de l'assistance