Building an App with Vue.js
It's a pleasure to welcome you to what I hope will be a series of tutorials on how to build a real life application using Vue.js as the front-end.
Here is the REPO
For better productivity of the code, I will use:
- Stylus for the CSS
- Pug (before Jade) for the HTML
- ES6 for the code
For the initial project structure, we are going to use Vue-cli
Vue-cli offers a simple way to create Vue-based apps. It’s just great and simple, and if you would like a step-by-step guide on how to use Vue-loader and the other tools, we can write a 0.1 tutorial on this, but for now Vue-cli will accomplish what we want.
Installing Vue-cli
Simply run npm install -g vue-cli
.
Creating the Project
This project lives inside this repo.
Let's start by creating a very simple app, with no tests, no routing, no eslint, just the base. The console should look like:
? Project name vue-app
? Project description VueJS Playground app
? Author ethaan <your.name@gmail.com>
? Vue build standalone
? Install vue-router? No
? Use ESLint to lint your code? No
? Setup unit tests with Karma + Mocha? No
? Setup e2e tests with Nightwatch? No
Folder Structure
Where...
- build is where the build options for
{dev | prod}
live - config contains webpack-related things and env variables for development
- src is where all the action will happen; this is where our front-end app will live
- static has any images or static files
- ...others babel, readme, editor, index.html and other files
Starting the App
We are going to use yarn because we are hipsters and fancy people (but you can use npm if you want)
yarn && yarn dev
If everything runs OK, a new browser should open and the console should look like:
DONE Compiled successfully in 3103ms
> Listening at http://localhost:8080
Writing our First Vue.js Component
First, start by removing the default Hello.vue
component by running rm src/components/hello.vue
and make sure src/App.vue
looks like the following:
<template></template>
<script>
export default {}
</script>
<style></style>
About App.vue
Vue, like React, is pretty simple in how it’s initially rendered into our HTML. If you take a look into src/main.js
, ignoring the comments, it just contains 8 lines of code (if you came from React World, you can think of this as
ReactDOM.render(<App />,document.getElementById('root'));
)
Hands-on Work
Okay, enough talk; it’s time to build the first component! Let’s try to build one component that uses the most of the Components API.
Component Structure
All the vue
components we are going to write use the following structure:
HTML
JS
CSS
This will make our life easier, so you don't need to open 3 different files to make a simple change; everything lives in the same file. Awesome, right?
Let’s start by creating our CodeMentorComponent.vue
inside the src
folder
and copy/pasting the structure of the App.vue
component.
Adding Pug (jade)
Since Vue.js is awesome, simply adding lang="jade"
into the template
tag like
<template lang="jade"></template>
will make our Vue component use Jade. You can read more at Single File Components.
This will leave us with a CodeMentorComponent
looking like this:
<template lang="jade">
.code-mentor-component
h1 Hello Codementor People
</template>
Pretty simple, right? But how do we use it on the App.vue component? The answer is components:{}
. Here, we are going to use the components
namespace.
This basically tells the component what components to use, so go into App.vue
and import the component and add the components
object:
<script>
import CodeMentorComponent from './components/CodeMentorComponent';
export default {
components: { CodeMentorComponent }
}
</script>
This will then use the Component CodeMentorComponent
:
<template lang="jade">
div
CodeMentorComponent
</template>
If everything runs OK, you should see this on the screen:
Now, let’s start doing some more interesting stuff.
We’ll start by creating a simple counter inside the CodeMentorComponent
component, for which we are going to use the data
method. In short this is a function that returns an {Object}. Our <script></script>
should look like this:
<script>
export default {
data() {
return {
counter: 0
}
}
}
</script>
And it can be called inside the <template></template>
:
p The counter value is: {{ counter }}
Still pretty simple. Let’s create a FormButton
component inside src/forms/Button.vue
. It should look like this:
<template lang="jade">
button(
@click="$emit('click')"
) {{ buttonText }}
</template>
<script>
export default {
props: {
buttonText: {
type: String,
default: 'Click',
}
}
}
</script>
At this point, our button component will look pretty simple, but let’s take a deeper look into the new things added here, $emit
, @click
and slot
.
@click is just a shorthand for v-on
directive.
$emit basically triggers an event that you previously attached to the component, so we can start thinking about how this component will be called, maybe something like Button(@click="onClick")
(but don’t worry about this code yet).
props This object represents what props the component will take, in this case buttonText
, which is expected to be of the type String
. Also, we are using default
to be click
. There are other options to pass into the props and ways to call them; you can check them out here.
Let’s continue...
Go ahead and import the button component inside CodeMentorComponent
, like so:
import FormButton from './forms/Button';
, and don’t forget to add it into the components
object:
components: { FormButton },
,
Next, call it on the <template></template>
:
FormButton(buttonText="Sum")
That’s how you pass the props, but what about computed values, or values inside the data
object? Simple, you can use the v-bind
directive:
First, our data object now looks like this:
data() {
return {
counter: 0,
sumButtonText: 'sum',
}
}
Then our FormButton
component is called:
FormButton(v-bind:buttonText="sumButtonText")
Nows let’s start some reactive work. We want to sum the value of counter
every time the button is clicked.
To accomplish that, we need to take in place methods
:
methods: {
onClick() {
this.counter = this.counter + 1;
}
},
Pretty simple, but wait, our button still doesn’t do anything! For that, we need to attach the method as an event on the component, using the shorthand @
, like:
FormButton(@click="onClick" v-bind:buttonText="sumButtonText")
And BAM, we got a reactive counter. Super cool, right? We are not done yet; we want to play with Vue, but what else we can do? We still have some basic directives to use.
Let’s try some fun things: let’s change the color of the counter depending on the counter. We’ll call that levels
, so let’s go and create the following object on the <script></script>
.
const COLORS_BY_LEVEL = {
normal: 'blue',
danger: 'red',
warning: 'yellow',
};
For that, we are going to integrate computed
property to our component, and this will look like:
computed: {
levelColor() {
const { counter } = this;
let level = 'normal';
if (counter > 3 && counter <= 8) level = 'warning';
else if (counter >= 9) level = 'danger';
return COLORS_BY_LEVEL[level];
}
},
Then, just call it on the span
:
p(v-bind:style=
{color: levelColor}) The counter Value is: {{ counter }}
Here we are using inline-style, but you can also do v-bin:class="klass"
, and have a klass
computed method that returns the class name you want.
Instead of a computed property, we can define the same function as a method instead. As far as the end result, the two approaches are exactly the same. However, the difference is that computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed. This means that, as long as {counter} has not changed, multiple access to the {levelColor} computed property will immediately return the previously computed result without having to run the function again.
From Computed Properties and Watchers - Basic Example.
This is awesome, right? Let’s keep playing. We’ll now add a second rest
button, and call it like so:
FormButton(@click="onClick" buttonText="rest")
BUT WHAT? the two buttons do exactly the same thing! Does this means I need to create two different methods just to sum || rest
? No — you can call methods
as normal function and pass parameters. Let’s get a little fancy.
We’ll change our buttons to look like this:
FormButton(@click="onClick({ action: 'sum'})" v-bind:buttonText="sumButtonText")
FormButton(@click="onClick({ action: 'rest'})" buttonText="rest")
and our onClick
method to look like the following:
methods: {
onClick({ action }) {
this.counter = action === 'sum' ? this.counter + 1 : this.counter - 1;
}
},
And we still have more stuff to do!
Let’s add more things into the component, and use the v-if
directive more here.
We’ll use the same logic for levels; let’s show a custom message (we can do this more cleanly by using a computed method and then just doing MESSAGE_BY_LEVEL[lebel]
, but let’s use some ifs).
Just add the following:
p(v-if="counter <= 3") HEY ALL OK
p(v-else-if="counter > 3 && counter <= 8") HEY HEY HEY! EASY
p(v-else) AAAAAAAA
There is another directive called
v-show
, and you can see the comparison here.
Now you have some ifs inside the components, reactive and working great.
Let’s use the last directive, v-for
more here.
Let’s. add an exclamation (!) for every positive count.
For this, just add the following:
.exclamations
h1(v-for="num in counter") !
and don't forget about the style (let's make them look cool)!
<style lang="stylus">
.exclamations
display: flex
</style>
Stylus loaders came already supported by Vue-cli; for more details, take a look on
vue-app/build/utils.js
.
Vue, you are so easy.
This is it for today! Hope that was enjoyable Don’t worry, more will come. We will be integrating GraphQL, express, Routing, Vuex (aka Redux for vue), and more interesting stuff. “We want to create {insertIdeaHere}”, you say — no problem! We can do whatever you all want. If you are itching to start building more cool stuff, take a look on Ethaan-Vuexpresso.
If you liked this post and want more like it, post in the comments what you’d like to see next!
div(v-if="peopleWantsMore)
ul
li(v-for="posts on ethanVuePosts)
Post(v-bind:post="post"
very helpful!
Gracias David