Introduction to Vue (part 2 of 2)

Marshall Shen
Babystep
Published in
11 min readAug 20, 2017
snapshot of demo, which is available at http://himarsh.org/tvinder

From part 1, you have learned the basics of Vue.js around components. Now let’s take it a step further and finish building the application!

You can find the demo application here, and you can find the Github code here.

In part 2, you will learn:

  1. How to handle events and transmit data
  2. How to use computed property
  3. (Advanced) How to use Vuex to manage states of an application

use events to transmit data

Now that we know how to create a method that does some action, we want to implement our like, dislike, and skip methods. In this step let’s wire up those actions to influence our like count.

We will make like increment the count by 1, dislike decrement the count by 1 and skip will not do anything to the count. So our actions component will cause an action that changes the value of likes, and our header component will have to know when likes and dislikes change and render that value.

Looking at the structure of the app, we see that actions and header are siblings, and children of the tvinder-app component. We know that we can use props to pass data from parent to child, but how can we pass information from child to parent? Every component has an event interface. This allows the component to listen to an event, and trigger an event.

We saw the first of this interface in the previous step where we used v-on to listen for a click event. Here we will want to do both, trigger an event for one of our three actions (i.e. like, dislike, skip), and listen for that event on the parent component to register that something has happened.

Let’s add some necessary HTML markup so that we can use click events to fire off our methods. We will use some icons for our likes and dislikes buttons. Here is the markup that will render those icons and also listen for click events that will trigger our three methods.

src/components/actions.js

... 
<div class="actions">
<div class="controls">
<div class="icon-button" v-on:click="decrement()">
<svg class="icon icon-cross"><use xlink:href="#icon-cross"></use></svg>
</div>
<a id="skip" href="#" v-on:click.prevent="skip" class="icon-buttdon">Skip</a>
<div class="icon-button" v-on:click="increment()">
<svg class="icon icon-heart"><use xlink:href="#icon-heart"></use></svg>
</div>
</div>
</div>
...

Now that we have some markup to work with, let’s actually create our methods.

src/components/actions.js

... 
methods: {
skip() {
const self = this
self.$emit('handleSkip')
},
increment() {
const self = this
self.$emit('handleLikes', 1)
},
decrement() {
const self = this
self.$emit('handleLikes', -1)
}
}
...

Here we are defining the three actions we want to perform. increment and decrement are equivalent to likeand dislike. So on click of the HTML element, each does the corresponding action.

The only thing that they do is $emit something. $emit() is part of the components event interface. Inside the parentheses, we must specify an eventName and an optional argument for any data we want to pass along. So increment() will emit an event with the name of handleLikes along with the value of 1. These events move upwards in the component tree. So the parent component of actions will be able to listen for an event with this name. 3. Now that we are emitting named events, we can listen for them on the parent component. We can capture these events that will trigger some action in the parent component.

src/components/app.js

... 
<div>
<app-header></app-header>
<movies :image_url="image_url"></movies>
<actions @handleLikes="handleLikes" @handleSkip="handleSkip" </actions>
</div>
...

The way you listen for these events is with a directive on the HTML element. We declare this directive with an @ symbol and the event name we are listening for. So when we $emit('handleLikes') from actions.js @handleLikes will respond to that event. The second part to this directive is the method to call. This is the ="handleLikes" part.

All this mean is that the app fires off the handleLikes method on the component as the response to whatever event we have captured. Next we need to implement the methods that will fire when the app receives those events.

app.js

... 
methods: {
handleLikes(vote) {
const self = this
self.likes += vote
self.incrementImage()
},
handleDislikes(vote) {
const self = this
this.dislikes += vote
self.incrementImage()
}
handleSkip() {
const self = this self.incrementImage()
},
incrementImage() {
// TODO
}
}
...

The last step is to add likes and disikes to our data function in app.js so that we can update that value, and pass it as a prop to our header component.

src/components/app.js

... 
<app-header :likes="likes" :dislikes="dislikes"></app-header>
...

src/components/header.js

... 
props: {
likes: {
type: Number,
required: true,
},
dislikes: {
type: Number,
required: true,
},
}
...

That’s it! Now we are updating our like count from our actions component.

use computed properties to render movie data

Here you will learn about computed properties! Now that we have most of the functionality built out, we will need to have some actual data to interact with. We have a json file with a list of movie objects with a movie name and a url that points to a poster of that movie. Up till now our templates have been very simple.

We have some data or some prop with a name and a value, and we simply bind that data in the template. But what if you have some data that is not a simple key value pairing? For instance, what if you had someone's name, but in the template you wanted to display their name backwards for some odd reason.

... 
<div>
{{ name.split('').reverse().join('') }}
</div>
...

That looks kind of ugly, and you can imagine there could be much more complex pieces of logic for information you would want to render in the template. This is where computed properties come in handy. They can all this complex logic and allow us to simply use the name of that property in the template.

... 
<div>
{{ reversedName }}
</div>
...
computed: {
reversedName() {
return this.name.split('').reverse().join('')
}

}
...

This cleans up our templates and allows us to render our data more efficiently! So much the same as methods, computed is a custom attribute Vue implements. Inside the computed attribute, we declare functions for all of our computed properties. Any time a piece of data is updated, its computed property will also be updated.

With this in mind, let’s think about how we will interact with our data. We have a list of movie objects, and each has a key value pair of name and image url. We have a movies component that can render the name and image of one movie at a time. So, we should pass a movie object as a prop to our movies component.

But how will we select which movie to display from the list? One way would be to just randomly select a movie from the movies list and pass that in as a prop. But what if order matters or we don't want to potentially see the same movie twice? We can keep track of an index in the list to select a movie from, and pass the movie at that index to our movies component.

First let’s capture our movie-data and set up the initial index of that list we want to render.

src/components/app.js

... 
data() {
return {
likes: 0,
dislikes: 0,
imageIndex: 0,
movieData: window.movieDataJson.posters

}
}
...

We have already set movieData on the window so that we can easily use this JSON. Now that we have some data to work with, let’s pass that movie object to our movies component.

src/components/app.js

... 
<div>
<app-header :likes="likes"></app-header>
<movies :movie="movie"></movies>
<actions @handleLikes="handleLikes" @handleSkip="handleSkip"></actions>
</div>
... computed: {
movie() {
const self = this
return self.movieData[self.imageIndex]
}

},
...

Here we are using a computed property to select which movie we will send as a prop to our movies component. We could also accomplish this directly in the template with something like <movies :movie="movieData[imageIndex]"></movies>, but using a computed property is a cleaner approach.

Now that we are passing the movie object, we need to update our movies component to use this object. This means more computed properties! Instead of taking the image url directly from a prop, we are now getting it from our movie object.

src/components/movies.js

... 
const html = `
<div class="movies">
<div class="movie-poster-container">
<img class="movie-poster" v-bind:src="extractImageUrl" v-bind:key="extractImageUrl">
<div class="movie-name">{{ extractImageName }}</div>
</div>
</div>
`
...
Vue.component("movies", {
template: html,
props: {
movie: {
type: Object,
required: true,
}
},
...
computed: {
extractImageUrl() {
const self = this return
self.movie.url
},
extractImageName() {
const self = this
return self.movie.name
}

},
)
...

Here we update our props to take in a movie object, and we define two computed properties to extract the information we want to bind to the template.

The last step is to implement our incrementImage() method on our app component so that when we like or dislike a movie, we cycle to the next movie.

src/components/app.js

... 
incrementImage() {
const self = this
self.imageIndex += 1 if(self.imageIndex > (self.movieData.length-1)) {
self.imageIndex = 0 }
}
...

set up Vuex

In this step we will introduce Vuex state management system for our app. For a small app like this, Vuex might not be necessary, but it is a good starting point to learn the core concepts of Vuex.

So what is Vuex? It is a centralized store for every component in the app. It ensures that its state can only be mutated in a predictable way. Why would we want to use Vuex? There can be many answers to this question — but two good cases for implementing Vuex in your app are when:

  1. multiple components depend on the same piece of state
  2. actions from different components have to mutate the same piece of state.

At it’s basic level a Vuex Store looks like this:

const Store = new Vuex.Store({ 
modules: { ... },
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... },
})

There are 5 core concepts to Vuex. They are state, actions, mutations, getters, and modules.

Let’s start by set up Vuex store, and we know that we want to keep track of likeand dislike , also specifically what movies are being liked or disliked. At the bottom we add window.store = Store so that the application has the store object.

src/store/index.js

((() => {  var Vuex = window.Vuex  const Store = new Vuex.Store({
state: {
likes: 0,
disLikes: 0,
likedMovies: [],
disLikedMovies: []
},

)}
window.store = Store}))()

Next we can define mutations that can change the states in the store. What is mutation? According to Vuex documentation:

The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument.

Let’s add mutation methods for each state:

src/store/index.js

((() => {  var Vuex = window.Vuex  const Store = new Vuex.Store({
state: {
likes: 0,
disLikes: 0,
likedMovies: [],
disLikedMovies: []
},
mutations: {
addToLikeCount(state, like) {
state.likes += like
},
addToDislikeCount(state, disLike) {
state.disLikes += disLike
},
addToDislikeArray(state, disLikedMovie) {
state.disLikedMovies.push(disLikedMovie)
},
addToLikeArray(state, likedMovie) {
state.likedMovies.push(likedMovie)
}
},

)}
window.store = Store}))()

Now that we have mutations to change states within the store, we now build actions for other components to use. What is action? According to Vuex documentation:

Actions are similar to mutations, the differences being that:

1. Instead of mutating the state, actions commit mutations.

2. Actions can contain arbitrary asynchronous operations.

Even though this project doesn’t use all the features actions can provide, such as asynchronous operations, having actions being called outside the store and keep mutations private within store is a good practice.

src/store/index.js

((() => {var Vuex = window.Vuexconst Store = new Vuex.Store({
state: {...},
mutations: {...},
actions: {
updateLikeCount(context, like) {
context.commit("addToLikeCount", like)
},
updateDisLikeCount(context, dislike) {
context.commit("addToDislikeCount", dislike)
},
updateDisLikedMovies(context, disLikedMovie) {
context.commit("addToDislikeArray", disLikedMovie)
},
updateLikedMovies(context, likedMovie) {
context.commit("addToLikeArray", likedMovie)
}
},

)}
window.store = Store}))()

wire up Vuex

Now that we have Vuex setup, we can use it in our app! We have app.js as the source of truth for data rendered in the rest of views, so we can modify getters and setters within app.js to talk to our Vuex store!

Let’s start with getters, we will get data from our store using computed property.

src/components/app.js

... 
<div>
<app-header :likes="likes"></app-header>
<movies :movie="movie"></movies>
<actions @handleLikes="handleLikes" @handleSkip="handleSkip"></actions>
</div>
...computed: {
movie() {
const self = this
return self.movieData[self.imageIndex]
},
likes() {
const self = this
return self.$store.state.likes
},
disLikes() {
const self = this
return self.$store.state.disLikes
}
},
...

next, we add setters to handle “like”, “dislike” or “skip” actions by using Vuex store. Whenever we want to trigger an action, we need to use store.dispatch method.

src/components/app.js

...computed: {},methods: {
handleLikes(vote) {
const self = this

self.$store.dispatch("updateLikeCount", vote)
self.$store.dispatch("updateLikedMovies", self.movie)
self.incrementImage()
},
handleDisLikes(vote) {
const self = this

self.$store.dispatch("updateDisLikeCount", vote)
self.$store.dispatch("updateDisLikedMovies", self.movie)
self.incrementImage()
},
handleSkip() {
const self = this

self.incrementImage()
},
}

...

toggle modals using conditionals

What if when we click on “votes” tab, we want to see a modal that shows details information of what we like or dislike? What if when we are done viewing that information, we want to close the modal and go back to where we were? This is where we can use v-show conditionals for toggling.

a detailed demo pops up when clicking on votes

First we need to create a component lists.js to capture the modal, and we get likedMovies and dislikedMovies from Vuex store. In addition, we use v-for to loop through the data collection.

src/components/lists.js

((() => {
const html = `
<div class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<p>Likes</p>
<div v-for="movie in likedMovies" id="likes">
{{ movie.name }}
</div>

<p>Dislikes</p>
<div v-for="movie in disLikedMovies" id="dislikes">
{{ movie.name }}
</div>

</div>
</div>
`
Vue.component("lists", {
template: html,
computed: {
likedMovies() {
const self = this
return self.$store.state.likedMovies
},
disLikedMovies() {
const self = this
return self.$store.state.disLikedMovies
}
},

}
})
}))()

Next we use v-show to toggle whether the modal should display or not. Essentially whenever v-show has a value true, Vue appends display:none CSS on the targeted element.

Who decides when to show the modal? We delegate that responsibility to the parent component v-show , and we pass a boolean prop showlists down to the child component. This pattern is called “data down”.

We first add the prop to the child component:

src/components/lists.js

((() => {
const html = `
<div class="modal" v-show="showLists">
<div class="modal-content">
<span class="close">&times;</span>
<p>Likes</p>
<div v-for="movie in likedMovies" id="likes">
{{ movie.name }}
</div>
<p>Dislikes</p>
<div v-for="movie in disLikedMovies" id="dislikes">
{{ movie.name }}
</div>
</div>
</div>
`
Vue.component("lists", {
template: html,
props: {
showLists: {
type: Boolean,
required: true,
}
},
computed: {
...
},
methods: {
closeModal() {
const self = this
self.$emit("closeModal")
}
}
})
}))()

Now what happens if we click the close button on lists.js ? Rather than having the child component directly change data, it calls$emit function to broadcast an event to its parent component, then the parent component knows what to do upon receiving that event. This pattern is called “event up”.

src/components/lists.js

((() => {
const html = `
<div class="modal" v-show="showLists">
<div class="modal-content">
<span class="close" v-on:click="closeModal()">&times;</span>
<p>Likes</p>
<div v-for="movie in likedMovies" id="likes">
{{ movie.name }}
</div>
<p>Dislikes</p>
<div v-for="movie in disLikedMovies" id="dislikes">
{{ movie.name }}
</div>
</div>
</div>
`
Vue.component("lists", {
template: html,
props: {...}, computed: {...}, methods: {
closeModal() {
const self = this
self.$emit("closeModal")
}
}

})
}))()

src/components/app.js

((() => {
const html = `
<div>
...
<lists :showLists="showLists" @closeModal="toggleShowLists"></lists>
</div>
`
Vue.component("tvinder-app", {
template: html,
data(){
return {
...
showLists: false,
}
},
computed: {...}, methods: {
...
toggleShowLists() {
const self = this
self.showLists = !self.showLists
}

}
})
}))()

Conclusion

That’s it! You’ve now successfully built a web application using Vue, congratulations! To recap, in this tutorial we have covered:

  1. Core functionalities of Vue components
  2. State management using Vuex
  3. How to handle communication across different Vue components

You can find part 1 of this tutorial here, the demo of the app is here, and the Github code is here.

Let’s take baby steps to learn tech!

--

--