A Full Day of Vue.js

05. Routing & Vue Router

Vue Router

Vue

Vuex

Vue Test Utils

 Routing

Routing often refers to splitting an applications UI  based on rules derived from the browser URL.

 Routing

https://www.twitter.com/

 Routing

https://www.twitter.com/{userName}
Dynamic route

 Routing

https://www.twitter.com/{userName}
protocol
hostname
pathname

 Routing

Routing allows us to...

- Refresh a page and keep our location in an app

- Bookmark a URL and share it with others

- Maintain browser back/forward history functionality

 Routing

Two main ways to achieve routing in a web application

Server Side Routing

Client Side Routing

The client (i.e. the browser) makes a request to the server on every URL change.

The client only makes a request to the server upon initial page-load.

First Meaningful Paint

Consistent SEO performance

Faster routing after initial load

Smooth transitions and changes

2

1

 Routing

Single-page applications

(SPAs)

Client side apps are often labelled as...

/charizard

/blastoise

/venusaur

 Routing

We'll build a Pokemon Routing app with:

Vue Router

 Routing

Starting Point

App

CharizardCard

 Routing

Starting Point

<template>
  <div class="card card--charizard has-text-weight-bold has-text-white">
      <div class="card-image">
        <div class="card-image-container">
          <img src="../../../static/charizard.png"/>
        </div>
      </div>
      <div class="card-content has-text-centered">
        <div class="main">
          <div class="title has-text-white">Charizard</div>
          <div class="hp">hp 78</div>
        </div>
        <div class="stats columns is-mobile">
          <div class="column">🔥<br>
            <span class="tag is-warning">Type</span>
          </div>
          <div class="column center-column">199 lbs<br>
            <span class="tag is-warning">Weight</span>
          </div>
          <div class="column">1.7 m <br>
            <span class="tag is-warning">Height</span>
          </div>
        </div>
      </div>
    </div>
</template>

<script>
export default {
  name: "CharizardCard",
};
</script>
<template>
  <div class="container">
    <div class="pokemon">
      <PokemonCard />
    </div>
  </div>
</template>

<script>
import CharizardCard from "./components/CharizardCard";

export default {
  name: "App",
  components: {
    PokemonCard: CharizardCard
  }
};
</script>

<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
CharizardCard.vue
App.vue

 Routing

src/
components/
BlastoiseCard.vue
CharizardCard.vue
VenusaurCard.vue
App.vue
main.js

 Routing

CharizardCard.vue
VenusaurCard.vue
BlastoiseCard.vue
App.vue

 Routing - Vue Router

Vue Router is the official router of Vue. It deeply integrates with Vue to make building Single Page applications a breeze.

 Routing - Vue Router

npm install vue-router --save

 Routing - Vue Router

Vue.use(VueRouter);
import Vue from 'vue';
import VueRouter from 'vue-router';

 Routing - Vue Router

routes

Responsible in mapping components to respective URL pathnames.

 Routing - Vue Router

const routes = [
  { path: "/", component: CharizardCard },
  { path: "/charizard", component: CharizardCard },
  { path: "/blastoise", component: BlastoiseCard },
  { path: "/venusaur", component: VenusaurCard },
  { path: "*",
    component: {
      template: "<div>Sorry! We couldn't find this Pokemon</div>"
    } 
  },   
];

 Routing - Vue Router

const router = new VueRouter({
  routes,
});
new Vue({
  el: '#app',
  router,
  render: h => h(App)
});

 Routing - Vue Router

src/
App.vue
main.js
components/
router.js

 Routing - Vue Router

import Vue from 'vue';
import VueRouter from 'vue-router';
import BlastoiseCard from './components/BlastoiseCard';
import CharizardCard from './components/CharizardCard';
import VenusaurCard from './components/VenusaurCard';

Vue.use(VueRouter);

const routes = [
  {path: '/', component: CharizardCard},
  {path: '/charizard', component: CharizardCard},
  {path: '/blastoise', component: BlastoiseCard},
  {path: '/venusaur', component: VenusaurCard},
  {
    path: '*',
    component: {
      template: `
        <h3 class="subtitle has-text-white">
          Sorry. We couldn't find that Pokémon :(.
        </h3>
      `
    }
  }
];

export default new VueRouter({
  mode: 'history',
  routes
});
router.js

 Routing - Vue Router

https://localhost:8080/#/blastoise

Hash mode URLs

Never sent to the server

 Routing - Vue Router

https://localhost:8080/blastoise

History mode URLs

 Routing - Vue Router

import Vue from "vue";
import App from "./App";
import router from "./router";

new Vue({
  el: "#app",
  router,
  render: h => h(App)
});
main.js

 Routing - Vue Router

<router-view />

Component responsible in rendering another component based on the app's location

 Routing - Vue Router

<router-link />

Component that allows a change in browser location without making a web request

 Routing - Vue Router

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

      <div class="pokemon-links has-text-centered">
        <router-link to="/charizard">/charizard</router-link>
        <router-link to="/blastoise">/blastoise</router-link>
        <router-link to="/venusaur">/venusaur</router-link>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "App"
};
</script>

<style lang="css" src="../styles.css">
// Styles from stylesheet
</style>
App.vue

 Routing - Vue Router

/charizard
/blastoise
/venusaur
/pikachu

 Routing - Vue Router

Vue Router

- Well Tested

- Consistency between different browsers

- Dynamic route matching

- Nested routes

- Navigation guards

...

 Routing - Vue Router

- routes

Responsible in mapping components to respective url pathnames.

- router-view
- router-link

Component responsible in rendering another specified component based on the app’s location.

Component that allows the user to change the location of the browser without making a web request

 Routing - Vue Router - Dynamic Route Matching

Dynamic Route Matching involves mapping routes with the same pattern to a single component.

 Routing - Vue Router - Dynamic Route Matching

src/
components/
BlastoiseCard.vue
CharizardCard.vue
EeveeCard.vue
GolbatCard.vue
MachampCard.vue
MoltresCard.vue
OnixCard.vue
VenusaurCard.vue
WigglytuffCard.vue
...Card.vue
AlakazamCard.vue
src/
components/
PokemonCard.vue

 Routing - Vue Router - Dynamic Route Matching

const routes = [
  {path: '/', component: CharizardCard},
   
   
   
  ...
];
  {path: '/pokemon/:id', component: PokemonCard}
  {path: '/charizard', component: CharizardCard},
  {path: '/blastoise', component: BlastoiseCard},
  {path: '/venusaur', component: VenusaurCard},

 Routing - Vue Router - Dynamic Route Matching

<template>
  ...
</template>

<script>
export default {
  name: "PokemonCard",
  created() {
    console.log(this.$route.params.id);
  }
};
</script>
PokemonCard.vue
/pokemon/charizard
charizard

 Routing - Vue Router - Dynamic Route Matching

data: [
  {
    id: 'charizard',
    hp: 78,
    type: 🔥,
    weight_lbs: 199,
    height_m: 1.7,
  },
  ...
]

 Routing - Vue Router - Dynamic Route Matching

export default [
  {
    id: "charizard",
    name: "Charizard",
    hp: 78,
    type: "🔥",
    weight_lbs: 199,
    height_m: 1.7
  },
  {
    id: "blastoise",
    name: "Blastoise",
    hp: 79,
    type: "💧",
    weight_lbs: 223,
    height_m: 1.6
  },
  {
    id: "venusaur",
    name: "Venusaur",
    hp: 80,
    type: "🍃",
    weight_lbs: 220,
    height_m: 2.0
  }
];
data.js

 Routing - Vue Router - Dynamic Route Matching

import Vue from "vue";
import VueRouter from "vue-router";
import PokemonCard from "./components/PokemonCard";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    component: {
      template: `
        <h3 class="subtitle has-text-white">
          Select a Pokémon from the links below!
        </h3>
      `
    }
  },
  {
    path: "/pokemon/:id",
    component: PokemonCard
  },
  {
    path: "*",
    component: {
      template: `
        <h3 class="subtitle has-text-white">
          Sorry. We couldn't find that Pokémon :(.
        </h3>
      `
    }
  }
];

export default new VueRouter({
  mode: "history",
  routes
});
router.js

 Routing - Vue Router - Dynamic Route Matching

<template>
  <div class="card has-text-weight-bold has-text-white"
    :class="'card--' + pokemon.id">
    <div class="card-image">
      <div class="card-image-container">
        <img :src="getPokemonImg"/>
      </div>
    </div>
    <div class="card-content has-text-centered">
      <div class="main">
        <div class="title has-text-white">{{pokemon.name}}</div>
        <div class="hp hp-venusaur">hp {{pokemon.hp}}</div>
      </div>
      <div class="stats columns is-mobile">
        <div class="column">{{pokemon.type}}<br>
          <span class="tag is-danger">Type</span>
        </div>
        <div class="column center-column">{{pokemon.weight_lbs}} lbs<br>
          <span class="tag is-danger">Weight</span>
        </div>
        <div class="column">{{pokemon.height_m}} m<br>
          <span class="tag is-danger">Height</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import pokemonData from '../data';

export default {
  name: "PokemonCard",
  data() {
    return {
      pokemon: {
        id: '',
        name: '',
        hp: 0,
        type: '',
        weight_lbs: 0,
        height_m: 0,
      },
    }
  },
  created() {
    this.getPokemon();
  }, 
  computed: {
    getPokemonImg() {
      // eslint-disable-next-line no-undef 
      return require(`../../static/${this.pokemon.id}.png`);
    }
  },
  watch: {
    $route() {
      this.getPokemon();
    }
  },
  methods: {
    getPokemon() {
      const idParam = this.$route.params.id;
      const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);

      if (pokemonFromData) {
        this.pokemon = pokemonFromData;
      }
    }
  }
};
</script>
PokemonCard.vue

 Routing - Vue Router - Dynamic Route Matching

Dynamic Route Matching with props is more ideal!

 Routing - Vue Router - Dynamic Route Matching

const routes = [
  ...,
  
  
  
  
  
  ...
];
  { 
    path: '/pokemon/:id',
    component: PokemonCard,
    props: true
  }

 Routing - Vue Router - Dynamic Route Matching

<template>
  ...
</template>

<script>
export default {
  name: "PokemonCard",
  props: ['id'],
  created() {
    console.log(this.id);
  }
};
</script>
PokemonCard.vue
/pokemon/charizard
charizard

How do we handle dynamic routes with unavailable params?

e.g.

https://localhost:8080/pokemon/pikachu

not available :(

<template>
  <div>
    <div v-if="pokemon.id">
      <!-- Display Pokemon Details -->
    </div>
    <div v-if="!pokemon.id">
      <h3 class="subtitle has-text-white">
        Sorry. We couldn't find that Pokémon :(.
      </h3>
    </div>
  </div>
</template>

<script>
  // ...
</script>
PokemonCard.vue

Nested Routes allows us to compose components multiple levels deep that corresponds to segments of a URL path.

 Routing - Vue Router - Nested Routes

 Routing - Vue Router - Nested Routes

App

PokemonCard

PokemonDetails

PokemonStats

 Routing - Vue Router - Nested Routes

/pokemon/:id/details
/pokemon/:id/stats
PokemonCard
PokemonDetails
PokemonCard
PokemonStats

 Routing - Vue Router - Nested Routes

const routes = [
  ...,
    







  
  
  
  

 

 

 

 


];
  { 
    path: '/pokemon/:id',
    component: PokemonCard,
    props: true,
    children: [
      {
        path: 'details',
        component: PokemonDetails,
        props: true
      },
      {
        path: 'stats',
        component: PokemonStats,
        props: true
      }
    ]
  }
router.js

 Routing - Vue Router - Nested Routes

<template>
  <div class="pokemon">
    <div class="card-image">
      ...
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {
  name: "PokemonCard",
  ...
};
</script>
PokemonCard.vue
Responsible in rendering sub-component for nested URL route.

 Routing - Vue Router - Nested Routes

src/
components/
PokemonCard.vue
PokemonDetails.vue
PokemonStats.vue
App.vue
main.js

 Routing - Vue Router - Nested Routes

<template>
  <div class="card-content has-text-centered">
    <div class="main">
      <div class="title has-text-white">{{pokemon.name}}</div>
      <div class="hp hp-venusaur">hp {{pokemon.hp}}</div>
    </div>
  </div>
</template>

<script>
import pokemonData from '../data';

export default {
  name: "PokemonDetails",
  props: ['id'],
  data() {
    return {
      pokemon: {
        name: '',
        hp: 0
      },
    }
  },
  created() {
    this.getPokemon();
  }, 
  watch: {
    id() {
      this.getPokemon();
    }
  },
  methods: {
    getPokemon() {
      const idParam = this.id;
      const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);

      if (pokemonFromData) {
        this.pokemon.name = pokemonFromData.name;
        this.pokemon.hp = pokemonFromData.hp;
      }
    }
  }
};
</script>
PokemonDetails.vue

 Routing - Vue Router - Nested Routes

<template>
  <div class="card-content has-text-centered">
    <div class="stats columns is-mobile">
      <div class="column">{{pokemon.type}}<br>
        <span class="tag is-danger">Type</span>
      </div>
      <div class="column center-column">{{pokemon.weight_lbs}} lbs<br>
        <span class="tag is-danger">Weight</span>
      </div>
      <div class="column">{{pokemon.height_m}} m<br>
        <span class="tag is-danger">Height</span>
      </div>
    </div>
  </div>
</template>

<script>
import pokemonData from '../data';

export default {
  name: "PokemonStats",
  props: ['id'],
  data() {
    return {
      pokemon: {
        type: '',
        weight_lbs: 0,
        height_m: 0
      },
    }
  },
  created() {
    this.getPokemon();
  }, 
  watch: {
    id() {
      this.getPokemon();
    }
  },
  methods: {
    getPokemon() {
      const idParam = this.id;
      const pokemonFromData = pokemonData.find(pokemon => pokemon.id === idParam);

      if (pokemonFromData) {
        this.pokemon.type = pokemonFromData.type;
        this.pokemon.weight_lbs = pokemonFromData.weight_lbs;
        this.pokemon.height_m = pokemonFromData.height_m;
      }
    }
  }
};
</script>
PokemonStats.vue

 Routing - Vue Router - Nested Routes

import Vue from "vue";
import VueRouter from "vue-router";
import PokemonCard from "./components/PokemonCard";
import PokemonDetails from "./components/PokemonDetails";
import PokemonStats from "./components/PokemonStats";

Vue.use(VueRouter);

const routes = [
  ...,
  {
    path: "/pokemon/:id",
    component: PokemonCard,
    props: true,
    children: [
      {
        path: "details",
        component: PokemonDetails,
        props: true
      },
      {
        path: "stats",
        component: PokemonStats,
        props: true
      }
    ]
  },
  ...
];

export default new VueRouter({
  mode: "history",
  routes
});
router.js

 Routing - Vue Router - Nested Routes

<template>
  <div class="card has-text-weight-bold has-text-white"
    :class="'card--' + id">
    <div class="card-image">
      <div class="card-image-container">
        <img :src="getPokemonImg"/>
      </div>
    </div>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "PokemonCard",
  props: ['id'],
  computed: {
    getPokemonImg() {
      // eslint-disable-next-line no-undef 
      return require(`../../static/${this.id}.png`);
    }
  }
};
</script>
PokemonCard.vue

Navigation Guards give us the ability to guard navigations by either redirecting it or cancelling it.

 Routing - Vue Router - Navigation Guards

 Routing - Vue Router - Navigation Guards

Three types of Navigation Guards

1. Global - for all navigation routes

2. Per-route - for a certain route

3. In-component - for certain components

 Routing - Vue Router - Navigation Guards

Global Navigation Guards

const router = new VueRouter({ ... });
router.beforeEach((to, from, next) => {
  ...
});
to.path
next();
next('/about');
next(false);

 Routing - Vue Router - Navigation Guards

Global After Hooks

const router = new VueRouter({ ... });
router.afterEach((to, from) => {
  ...
});

After hooks are not navigation guards since the route is already complete at that moment!

 Routing - Vue Router - Navigation Guards

const routes = [
  ...,
    








];
  { 
    path: '/pokemon/:id',
    component: PokemonCard,
    props: true,
    beforeEnter: (to, from, next) => {
      ...
    }
  }

Per-Route Navigation Guards

 Routing - Vue Router - Navigation Guards

In-Component Navigation Guards

<template>
  ...
</template>

<script>
export default {
  name: "PokemonCard",
  beforeRouteEnter(to, from, next) {},
  beforeRouteUpdate(to, from, next) {},    beforeRouteLeave(to, from, next) {},
};
</script>

 Routing - Vue Router - Navigation Guards

import ...;

Vue.use(VueRouter);

const routes = [
  ...,
  {
    path: "*",
    component: {
      template: `
        <h3 class="subtitle has-text-white">
          Sorry. We couldn't find that Pokémon :(.
        </h3>
      `
    },
    beforeEnter: (to, from, next) => {
      console.log("Are you looking for a Pokemon? Use the links below!");
      next();
    }
  }
];

export default new VueRouter({
  mode: "history",
  routes
});
router.js

 Routing - Vue Router - Navigation Guards

<template>
  ...
</template>

<script>
import pokemonData from '../data';

export default {
  name: "PokemonCard",
  ...,
  beforeRouteUpdate(to, from, next) {
    if (to.path === '/pokemon/venusaur') next('/pokemon/charizard');
    else next();
  },
  ...
};
</script>
PokemonCard.vue

 Routing - Vue Router

Vue Router has more functionality:

 Routing - Vue Router

- Vue Router Documentation

- Let's build a Custom Vue Router

- Understanding client side routing with Vue.js: JSCamp18

 Routing - Chapter Summary!

  • Routing refers to splitting an applications UI based on rules derived from the browser URL.

  • Client-side applications are often labelled as Single-Page applications since the server provides a single template.

  • Vue Router is Vue's official client side routing library.

  • routes: Array that maps components to respective URL pathnames.

  • <router-view>: Component responsible in rendering another specified component based on the app's location.

  • <router-link>: Component that allows the user to change the location of the browser without making a web request.

  • Vue Router features we've seen

  • Dynamic Route Matching: mapping routes with the same pattern to a single component.

  • Nested Routes: compose components multiple levels deep that corresponds to segments of a URL path.

  • Navigation Guards: guard navigations by either redirecting it or cancelling it.

 Routing - Exercise 💪!

 Routing - Exercise 💪!

App

CityComponent

 Routing - Exercise 💪!

Starting Point

 Routing - Exercise 💪!

Vue Router

Creates routes to each different city with paths like ''/city/los-angeles".

Solution 👀

Create a router instance (e.g. router.js) that exports a router instance and contains a routes array.

Vue Router/Dynamic Routes

Creates a single dynamic route for the different cities: "/city/:id".

Solution 👀

Create a router instance (e.g. router.js) that exports a router instance and contains a routes array.

Create simple routes for homepage/not-found screens.

Create simple routes for homepage/not-found screens.

🎉

A Full Day of Vue.js Workshop - 05 Routing (slimmed)

By djirdehh

A Full Day of Vue.js Workshop - 05 Routing (slimmed)

  • 298