Layouts y Vuex Modules

10/27

Lectura

Aunque la app se ve mejor sigue estando la falta de estilo, ya que se ve todo alineado a la izquierda.
AlLeft

Para arreglar esto vamos a hacer uso de las clases CSS de Bootstrap y a crear un Layout. Con los layouts conseguimos reutilizar código o componentes que se utilizan en todas las páginas, implementándolo una sola vez.
Creamos, dentro de la carpeta /layouts un archivo con el nombre MainLayout.vue

Y le damos el siguiente contenido:

<template>
  <div class="container">
    <router-view/>
  </div>
</template>

<script>

export default {
  name: 'MainLayout'
}
</script>

📗 La etiqueta <router-view /> es parte de la API de Vue Router. Lee la documentación aquí: https://router.vuejs.org/api/#router-view

Todas nuestras vistas que hagan match a una ruta se van a inyectar dentro de esta etiqueta, <router-view />.
Por lo tanto, en el layout principal podemos poner componentes como la barra de navegación (Navbar) o el footer, que van a ser iguales para todas las páginas.
Lo más probable es que dichos componentes no cambien entre páginas, por lo que es perfecto que estén aquí.

Los componentes de tipo layout (recuerda, en Vue todo son componentes), son útiles cuando queremos tener varios tipos de páginas, por ejemplo, podemos tener un layout específico para la página del Login, otro para las páginas de error y otro layout para el resto de páginas.

En este curso vamos a crear 2 layouts distintos, uno para el loading y otro para el resto de páginas.
Debes crear los layouts según las necesidades de tu proyecto.


Ya tenemos el layout principal de nuestra app. Vamos a crear el layout de Loading, y para ello tenemos que crear otro componente dentro de la carpeta /layouts, con el nombre LoadLayout.vue y le dotamos del siguiente contenido:

<template>
  <div class="loading-layout">
    <slot/>
  </div>
</template>

<script>

export default {
  name: 'LoadLayout'
}
</script>

📗 Un Slot en Vue es una funcionalidad inspirada en la especificación de Web Components con la finalidad de poder definir zonas de contenido distribuido. Puede contener cualquier plantilla de código, incluyendo HTML.
Lee más acerca de los slots de Vue aquí: https://es.vuejs.org/v2/guide/components-slots.html

Bien, ya tenemos nuestro componente Loading, nuestro MainLayout y nuestro LoadingLayout, pero nos sigue faltando un elemento. ¿Cómo controlamos si debemos mostrar el layout de Loading o el layout principal?
Vamos a crear una variable en nuestro Store que nos indique el estado de nuestra app, es decir, si está loading o no.

Esto lo vamos a usar durante la petición del token de autenticación, es decir, mientras hacemos la llamada HTTP estaremos mostrando el LoadingLayout con el componente Loading.

Por lo tanto, vamos a nuestra carpeta de /store/modules y creamos un nuevo módulo (archivo) llamado loading.js, que es el que va a gestionar el estado Loading inicial de nuestra app.
No olvidemos que esto es solo una práctica y lo más probable es que hacer esto en un proyecto pequeño (como este) no tenga sentido.

// store/modules/loading.js

export default {
  namespaced: true,
  state: {
    isLoading: false
  },
  mutations: {
    SET_LOADING (state, payload) {
      state.isLoading = payload
    }
  }
}

Hemos creado una variable (estado) isLoading con un espacio de nombres y una función (mutación) que cambia el valor de isLoading. Extremadamente sencillo, pero vamos a ver como trabajar con módulos y namespaces con Vuex.

Ahora sí, tenemos todos los ingredientes necesarios para crear nuestro Loading mientras se hace la petición POST para obtener el token.

Por defecto, isLoading tiene valor false, por lo tanto, el primer paso es cambiarlo a true cuando estemos haciendo la petición de token.
Esto lo hacemos desde nuestro módulo oauth.js del Store, en nuestra función (acción) getToken.

export default {
  // ...
  actions: {
    getToken ({ commit }) {
      commit('loading/SET_LOADING', true, { root: true })

      oauth.getToken()
        .then(({ data }) => {
          commit('SET_ACCESS_TOKEN', data.access_token)
        })
        .catch((err) => {
          commit('SET_ACCESS_TOKEN', null)
          console.log('Error OAuth: ', err)
        })
        .finally(() => {
          commit('loading/SET_LOADING', false, { root: true })
        })
    }
  }
}

Mientras dure la llamada HTTP ponemos el valor a true y cuando termine (con error o con éxito) lo ponemos a false.
Lo bueno de tener namespaces con Vuex, aparte de modularizar por funcionalidad, es que no tendremos problemas con el nombrado de nuestras acciones y mutaciones porque llevan el prefijo del módulo, en este caso loading/XXX.

Ahora sí, ya tenemos todo listo para ir a nuestro fichero App.vue y decirle que mientras este esperando al token muestre el loading, y que cuando termine, muestre el layout principal, que contiene las vistas.

Aprovechamos para borrar los enlaces que venían por defecto. Nuestro fichero App.vue, en el bloque de template, es decir el HTML, se vería así:

<template>
  <div id="app">

    <LoadLayout v-if="isLoading">
      <BaseLoading/>
    </LoadLayout>

    <MainLayout v-else/>

  </div>
</template>

Y la parte del <script> tendría este contenido:

<script>
import { mapState } from 'vuex'

import LoadLayout from './layouts/LoadLayout'
import MainLayout from './layouts/MainLayout'
import BaseLoading from '@/components/BaseLoading.vue'

export default {
  name: 'App',
  components: {
    MainLayout,
    LoadLayout,
    BaseLoading
  },
  computed: {
    // Uso: mapState(moduleName, { state })
    ...mapState('loading', {
      isLoading: 'isLoading'
    })
  }
}
</script>

📗 Lee más acerca de los mapState de vuex aquí: https://vuex.vuejs.org/guide/state.html#the-mapstate-helper

El proceso es el siguiente:

  • Desde Vuex nos traemos la funcionalidad de mapState
  • Importamos los 3 componentes que necesitamos (los 2 layouts que acabamos de crear y el componente de Loading)
  • A través de mapState podemos acceder a los valores del estado de nuestro módulo (en este caso nos interesa el módulo de loading) y utilizarlos en nuestro componente.
    • Para usar mapState con namespaces lo único que tenemos que hacer es indicarle, antes de empezar a mapear el estado, a qué módulo nos referimos, es decir, ‘loading’. El nombre del módulo se corresponde con el nombre del fichero.
      Como el módulo se llama loading.js tendremos que usar de nombre loading (sin la extensión). Con esto le decimos que solo nos interesa el estado del loading.
  • Con las directivas v-if y v-else mostramos un componente u otro según esté cargando o no. Si está loading mostramos loading, en caso contrario mostramos la vista.

En el navegador, abrimos las Herramientas para desarrolladores (o DevTools), vamos a la pestaña de console y recargamos la app. Deberías ver el siguiente error:
Vuex-Err
¿Adivinas por qué es? Efectivamente, no hemos dado de alta el módulo en nuestro Store. Vamos a ello. Desde el fichero de /store/index.js agregamos lo siguiente:

import Vue from 'vue'
import Vuex from 'vuex'

import oauth from './modules/oauth'
import loading from './modules/loading'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    oauth,
    loading
  }
})

Si vas al navegador deberías ver, si no hay ningún error, el componente Loading durante un pequeño espacio de tiempo y que después se carga la vista de la página Home
Loading

El código completo de App.vue es el siguiente:

<template>
  <div id="app">

    <LoadLayout v-if="isLoading">
      <BaseLoading/>
    </LoadLayout>

    <MainLayout v-else/>

  </div>
</template>

<script>
import { mapState } from 'vuex'

import LoadLayout from './layouts/LoadLayout'
import MainLayout from './layouts/MainLayout'
import BaseLoading from '@/components/BaseLoading.vue'

export default {
  name: 'App',
  components: {
    MainLayout,
    LoadLayout,
    BaseLoading
  },
  computed: {
    ...mapState('loading', {
      isLoading: 'isLoading'
    })
  }
}
</script>

<style lang="stylus">
  #app
    padding 60px 0
    font-family 'Avenir', Helvetica, Arial, sans-serif
    -webkit-font-smoothing antialiased
    -moz-osx-font-smoothing grayscale
    color #ffffff
    background-color #15202b
</style>

Vamos a la siguiente lectura, en la que veremos cómo mejorar nuestro _Layout

Aportes 10

Preguntas 2

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesión.

Aquí les dejo el fix.

En la parte de store/index.js debemos agregar el modulo de Loading por lo que debería quedar así:

import Vue from'vue'
import Vuex from'vuex'

import oauth from'./modules/oauth'
import loading from'./modules/loading'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    loading,
    oauth
  }
})

Y en App.vue debería quedar así en la parte de Script:

import MainLayout from '@/layouts/MainLayout'
import LoadLayout from '@/layouts/LoadLayout'
import BaseLoading from '@/components/BaseLoading'

export default Vue.extend({
  name: 'App',
  components: {
    MainLayout,
    LoadLayout,
    BaseLoading
  },
  computed: {
    isLoading () {
      return this.$store.state.loading.isLoading
    }
  }
})

Hola, hasta ahora todo genial. Solo me preguntaba si hay alguna razon particular por la cual usamos mayusculas para algunos metodos por ejemplo: SET_LOADING, SET_ACCESS_TOKEN, SET_LOADING ?

Son metodos reservados o es solo un estandar o buena practica?

Muchas gracias de antemano.

Perfecto, aunque tengo dos cuantas dudas:

1.- Tengo entendido que por buenas prácticas (Y esto se menciona en los cursos anteriores), el v-if solo se debe usar en componentes que vamos a renderizar una sola ves, en este caso, ocuparemos el loader en diferentes ocasiones, por lo que usar v-if podría consumir más recursos de los esperado, ¿No sería mejor usar v-show? Así solamente mostramos el loading cuando lo necesitemos y cuando no solo se oculta con CSS (v-show), así no estamos rendereando una y otra vez el loader, aunque claro, esto igual puede dar problemas, porque si en el layout principal usamos propiedades que no están disponibles aún, al usar v-show dará un error, pero con v-if no habrá problema

2.- Ya que el loader lo usaremos mucho, ¿No sería más práctico importarlo como un componente global? Algo así como Bootstrap-vue que no necesitamos importarlo en cada componente que usemos?

Muy guay el tema de los namespaces de Vuex!!

¿Que significa el {root:true} dentro de commit(‘loading/SET_LOADING’, true, { root: true })?

Como pequeño aporte me gustaría añadir que a_ mapState_ también es posible enviarle como segundo paramétro un Array de elementos con los estados que queremos traernos, que a mí personalmente, me gusta más como queda:

//...mapState('loading', { isLoading: 'isLoading })
...mapState('loading', ['isLoading'])

Seguimos con el curso !! 👍🏻

Hola, estuve copiando y probando el código, salió todo ok. Pero a veces en la consola me daba error de identacion o falta linea de espacio al final de cada archivo. Hay alguna manera de solucionarlo sin estar yendo linea por linea? Ejemplo de errores:
23:1 error Expected indentation of 6 spaces but found 8 indent
47:2 error Newline required at end of file but not found eol-last

Compañeros, leí la documentacion de Vuex pero la verdad no me queda muy claro como funciona el el spread operator en el mapState(), Gracias!

😛

Hola! en esté y varios documentos más están cortadas algunas etiquetas de cierre de componentes y el contenido está incompleto.

Genial! no tengo todavía muy claro para que sirve namespaced: true alguien me explica por favor?