materials

Siguientes cosas a aprender en Vue

Algunas cosas interesantes que nos pueden ser útiles en nuestros proyectos son:

Autenticación

Una parte importante de cualquier aplicación es la autenticación de usuarios. Una de las formas más usadas y sencillas de autenticarnos frente a una API es el uso de tokens: cuando nos logueamos la API nos pasa un token y en cada petición que le hagamos debemos adjuntar dicho token en las cabeceras de la petición, tal y como vimos al final del tema de axios.

Aparte de eso, que es lo básico, hay muchas más cosas que podemos incluir en nuestras aplicaciones. Por ejemplo vamos a hacer una aplicación que:

Veamos el código para hacer todo esto:

Store

// Fichero '@/store/index.js'
...
mutations: {
    loginUser(state, token) {
        state.token = token
        localStorage.token = token
    },
    logoutUser(state) {
        state.token = null
        localStorage.removeItem('token')
    },
},
actions: {
    login(context, user) {
        return new Promise ((resolve, reject) => {
            API.users.login(user)
            .then((response) => {
                context.commit('login', response.data)
                resolve(response.data)
            })
            .catch((err) => reject(err))
        })
    },
    ...
},

La acción que envía las credenciales del usuario al servidor es una promesa porque el componente Login.vue tiene que saber cuándo se obtiene el token para redireccionar a la página correspondiente.

Las mutaciones almacenan el token en el store y también en el localStorage.

API

// Fichero '@/services/API.js'
import axios from 'axios'
import store from '@/store'
import router from '@/router'

const API_URL = process.env.VUE_APP_API

const users = {
    login: (item) => axios.post(`${API_URL}/auth/login`, item),
    register: (user) => axios.post(`${API_URL}/auth/signup`, user),
}
...

axios.interceptors.request.use((config) => {
    const token = store.state.user.access_token
    if (token) {
        config.headers['Authorization'] = 'Bearer ' + token
    }
    return config;
}, (error) => {
    return Promise.reject(error)
})

axios.interceptors.response.use((response) => {
    return response
}, (error) => {
    if (error.response) {
        switch (error.response.status) {
            case 401:
                store.commit('logout')
                if (router.currentRoute.path !== 'login') {
                    router.replace({
                        path: 'login',
                        query: { redirect: router.currentRoute.path },
                    })
                }
        }
    }
    return Promise.reject(error)
})

export default {
    users,
    ...
};

Interceptamos las peticiones para incluir el token si lo tenemos.Y también las respuestas porque si es un error 401 hay que loguearse por lo que se cambia el router al login pero se le pasa la dirección de la página en la que se estaba para que tras loguearse se cargue esa página y no la de inicio.

Login.vue

// script de la vista 'Login.vue'
...
  mounted() {
    if (localStorage.token) {
      // Si el token caduca debemos comprobar que no haya expirado
      this.$store.commit("login", localStorage.token)
      this.loadPage()
    }
  },
  methods: {
    submit() {
      this.$store.dispatch("login", this.user)
        .then(() => this.loadPage())
        .catch((err) => alert(err))
    },
    loadPage() {
      const redirect = decodeURIComponent(this.$route.query.redirect || '/')
      this.$router.push({ path: redirect })
    }
  },
...

Router

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '../store'

import Datos from '../views/Datos.vue'
...

Vue.use(VueRouter)

router.beforeEach((to, from, next) => {
  if (to.meta.requireAuth) {
    if (store.token) {
      next();
    }
    else {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    }
  }
  else {
    next();
  }
})

const routes = [
  {
    path: '/datos',
    name: 'Datos',
    component: Datos,
    meta: {
      requireAuth: true,
    },
  },
  ...
]

export default router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

Para ver algunos ejemplos de cómo gestionar la autenticación en nuestros proyectos Vue podemos consultar cualquiera de estos enlaces:

Paso a producción

Una vez acabada nuestra aplicación debemos general el build que pasaremos a producción. El build es el conjunto de ficheros compilados, minificados, etc que subiremos al servidor de producción. Para ello tenemos un script en el package.json que se encarga de todo:

npm run build

Crea un directorio dist con lo qie hay que subir a producción:

Vuetify

Son varias las librerías para Vue que nos facilitan enormemente la creación de nuestros componentes ya que nos dan un código para los mismos (tanto el template como el Javascript) de manera que simplemente personalizando ese código con nuestros datos ya tenemos un componente totalmente funcional. Entre ellas están Material Design, ElementUI, Vuetify y muchas otras.

Podemos ver la utilidad de estas librerías consultando, por ejemplo, como crear una Datatable con Vuetify. Vuetify sigue el diseño de Material Design.

Podemos obtener toda la información sobre esta librería en su página web.

Instalación

Vue se instala como cualquier otro plugin:

vue add vuetify

Crear el layout

En App.vue borramos todo su contenido y lo sustituimos por el código de layout que deseemos de Vuetify. Para ver el código pinchamos en la imagen del layout deseada y lo copiamos.

A continuación ponemos el <router-view> donde corresponda.

Cada elemento del menú es una etiqueta <v-list-tile> dentro del <v-navigation-drawer>. Para modificar el menú vamos a Vuetify -> UI components -> Navigation drawers.

Para cada elemento que queramos añadir:

<v-list-tile-title>
  <routerlink :to="{ name: 'perfil' }">Perfil</router-link>
</v-list-tile-title>

Si no nos gusta Material Dessign tenemos alternativas como Buefy (que proporciona componentes Vue basados en Bulma) y muchas otras.

Saber más

Typescript

Es Javascript al que se le ha incorporado tipado de datos y otras utilidades. En los apuntes puedes ver una introducción a cómo usarlo en Vue y en Internet tienes infinidad de recursos para aprender más.

SSR (Server Side Rendering)

Esta tecnología permite que al obtener la página un robot (haciendo curl miURL) no devuelva sólo la <app> sino el HTML para que los robots la puedan indexar correctamente.

El problema que tiene una SPA es que las rutas no existen realmente sino sólo en el front y se generan asíncronamente, lo que dificulta a los robots obtener las páginas de las distintas rutas.

SSR hace que la primera vez que un usuario accede a la web se sirve entera desde el servidor y el resto de veces ya se sirve desde el front. Eso permite que a un robot se le sirva toda desde el servidor y la puede indexar. Esto no es algo que nos interese en todos los proyectos, sólo en aquellos en que sea importante que estén bien posicionados en los buscadores.

Más info: Server-Side Rendering.

Explicación de qué es y cómo funciona en Angular: Angular & SEO

Crear aplicaciones móviles con Vue

Diferentes librerías nos permiten que nuestras aplicaciones puedan ejecutarse en móviles tanto Android como iOS. La mayoría utilizan la librería Cordova de Apache2 para tener acceso a los elementos del móvil como notificaciones, cámara, geolocalización, …

Existen muchos pero las más utilizadas hoy en día son Quasar, Vue native e Ionic.

Quasar

Quasar es un framework basado en VueJS que te permite generar la aplicación de escritorio y la aplicación móvil tanto para Android como para iOS.

Tiene licencia MIT y su UI sigue las guías de Material. Su ventaja sobre los otros es que está creado en Vue y pensado para este framework.

Vue Native

Vue native es otro framework que permite generar aplicaciones móviles nativas usando Vue. En realidad es una capa sobre React Native que permite a Vue usar su API.

Con ella podemos acceder a los diferentes dispositivos del móvil como la cámara, la geolocalización, el acelerómetro, … Podemos encontrar en Internet muchos ejemplos de cómo hacer nuestra App con este framework, como este de scotch.io.

Ionic Vue

Ionic es posiblemente el Framework más utilizado para crear aplicaciones móviles nativas a partir de nuestra aplicación web. Está basado en Angular pero desde diciembre de 2020 puede usarse directamente en Vue, y es compatible con Vue3 y su Composition API.

Nuxt

Nuxt es un framework basado en Vue que crea un scaffolding de Vue con todo lo necesario para una aplicación media-grande (incluye rutas, Vuex,…) lo que nos facilita el desarrollo de nuestros proyectos.

También hay otras librerías que nos pueden ser de utilidad como:

Conclusión

Como vés existen infinidad de librerías alrededor de Vue para ofrecernos nuevas funcionalidades. Son tantas que el equipo de Vue ha creado AwesomeVue donde se registran parte de estas librerías y a donde podemos acceder en busca de cualquier cosa que necesitemos.

Angular

Aunque el crecimiento de Vue es muy importante, Angular sigue siendo aún el framework Javascript más demandado por las empresas. Si quieres aprender aquí tienes algunos enlaces de utilidad:

Vue con Laravel

Es sencillo crear una SPA completa usando Vue en el Front-end y Laravel para crear el Back-end que sirva la API. Podemos hacerlo como dos proyectos independientes o integrando Vue en Laravel.

Como proyectos independientes es la forma más sencilla. Simplemente nuestro proyecto Vue hará peticiones a la API desarrollada en Laravel.

Si queremos integrar Vue dentro del proyecto Laravel el funcionamiento es el siguiente:

Vamos a ver en detalle cómo gestionarlo.

Creación del proyecto

Creamos el proyecto Laravel. Dentro del mismo instalamos los paquetes que necesitemos para Vue:

laravel new laravue
cd laravue
npm install
npm i -S vue-router

Configuramos el proyecto en Vue

Configuramos el router de Vue en un nuevo fichero JS (por ejemplo /resources/js/router.js) y lo importamos en el fichero principal, /resources/js/app.js (el equivalente al main.js de un proyecto con vue-cli):

// Fichero app.js
...
import App from './views/App'
import router from './router'

const app = new Vue({
    el: '#app',
    components: {
        App
    },
    router,
});

Creamos el fichero /resources/js/App.vue que será el equivalente al App.vue de los proyectos vue-cli:

<template>
    <div>
        <h1>Vue Router Demo App</h1>

        <p>
            <router-link to="/">Home</router-link>
            ...
            <router-link to="/about">Sobre nosotros...</router-link>
        </p>

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

Configuramos Laravel

Creamos la vista principal en /resources/views/spa.blade.php:

<!DOCTYPE html>
<html lang="">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="">
    <title>Vue SPA Demo</title>
</head>
<body>
    <div id="app">
        <app></app>
    </div>

    <script src=""></script>
</body>
</html>

NOTA: la línea del <meta CSRF-TOKEN> es para evitar los errores de la consola por no pasar el token csrf.

Configuramos /routes/web.php para que sirva siempre esa página:

Route::get('/{any}', 'SpaController@index')->where('any', '.*');

para lo que creamos el controlador:

php artisan make:controller SpaController
https://vuex.vuejs.org/guide/forms.html```
y lo editamos:
```php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class SpaController extends Controller
{
    public function index()
    {
        return view('spa');
    }
}

Compilamos Vue

Ahora simplemente ejecutamos en la terminal

npm run dev

y ya tenemos la aplicación en marcha. Si aparece un error de “The Mix manifest does not exist” ejecutaremos npm run prod que crea el fichero mix-manifest.json.

Para que se compilen automáticamente los cambios que vayamos haciendo en Vue mientras desarrollamos el proyecto ejecutamos npm run watch-poll en una terminal.

Creamos la API

Para obtener datos de una API debemos en primer lugar crear la ruta en /routes/api.php:

Route::namespace('Api')->group(function () {
    Route::get('/alumnos', 'AlumnosController@index');
});

Esto nos crea sólo la ruta para el verbo GET. Una opción mejor es crear todas las rutas del recurso con:

Route::resource('alumnos',AlumnosController,['only'=>['index','store','show','update','destroy' ]]);

La opción only es opcional y permite restringir las rutas que se crearán para que no se muestren las que no utilizaremos (podemos comprobarlo con un php artisan route:list).

Otra opción es usar apiResources que crea sólo funciones para los métodos API:

Route::apiResource('alumnos',AlumnosController);

También podemos crear las rutas para varios controladores a la vez con resources en vez de resource:

Route::resources(
  [
    'alumnos' => 'Api\AlumnosController',
    'profes' => 'Api\ProfesoresController',
  ],
  ['only'=>['index','store','show','update','destroy' ]]
);

Luego creamos el controlador y el recurso:

php artisan make:controller Api/AlumnosController --api

La opción --resource (o -r) crea automáticamente los puntos de entrada para los métodos indicados. La opción --api es igual pero no crea funciones para los métodos create ni edit.

y el recurso:

php artisan make:resource AlumnoResource

Un recurso es un modelo que se debe transformar a un objeto JSON (lo que necesitamos en una API).

y editamos el controlador:

<?php

namespace App\Http\Controllers\Api;

use App\Alumno;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Resources\AlumnoResource;

class AlumnosController extends Controller {
    public function index()  {
        return AlumnoResource::collection(Alumno::paginate(10));
        // Esto devuelve, además del data información para paginar la salida

        // lo anterior equivaldría, sin usar el recurso, a
        $alumnos=Alumno::all()->toArray();
        return response()->json($alumnos);        
    }

    public function show($id)  {
       return new AlumnoResource(Alumno::find($id));
    }
    
    public function store(Request $request)  {
    
        $alumno = Alumno::create([
            'alumno_id' => $request->alumno()->id,
            'nombre' => $request->nombre,
            'apellidos' => $request->apellidos,
            ...
        ]);

      return new AlumnoResource($alumno);
    }
}

Saber más