Building a simple chat app with Vue.js

Alen Ajam
6 min readAug 1, 2021

--

When it comes to web-app solutions, React is clearly the winner here, both by popularity and satisfaction. But what about the newcomer Vue.js? Its community has been steadily growing in the last years, as its ecosystem. Let’s take a look!

About Vue.js

Vue.js is a progressive web framework, designed to be incrementally adoptable, in that it can easily scale from a library to a batteries-included framework. It’s easily approachable through its core library, which takes care of the view layer by heavily leveraging standard languages in the front-end development scenario. At the same time it’s capable of powering more complex apps in combination with its well integrated ecosystem of companion libraries.

Its main perk is definitely the shallow learning curve: It’s very easy to get started with your first app with not much prior knowledge, as opposed to other solutions, e.g. the huge set of APIs in Angular, or the JSX syntactic sugar in React.

Now that sounds great, but let’s put it to the test shall we? We will only utilise the core library for this example.

Let’s build a simple chat app

We are going to build a realtime chat app, which will show the last messages and allow the user to write a new one. The backend will be built on Firebase but we are not going to dive into that part here. Here’s what it will look like at the end:

We will bootstrap our project with the following command, which will guide us through a simple wizard:

> npx @vue/cli create vue-chat-app

We will then choose the following preset:

> Default ([Vue 2] babel, eslint)

Great! Let’s start coding. So how are things done in Vue.js? Vue.js apps are made up of components, just like Angular and React apps. You will see how those are different here though. There are a couple ways you could build them, but we will stick to the single file component way as it brings some advantages.

Single file component

Single file components have the .vue extension and are made up of three main blocks:

  • Template: The layout part of the component, any HTML is supported on here, but Vue.js will provide us with some cool extra features.
  • Script: Any Javascript is supported on here. This is where the data and logic of the component are handled. The object you see exported is the component itself, which we will eventually populate further.
  • Style: The style which will be injected onto the component, any CSS is supported on here. Given the scoped attribute, this style will be scoped to this one component.

This structure allows us to develop the whole component in the very same file, while maintaining the layers separated (but still coupled). This will eventually make the job easier. This is made possible by Webpack’s building process under the hood.

We will now build the components which we will eventually plug in into the App.vue component, which is the root of our project. We will put those into the /src/components directory.

Message.vue

This is a simple box which will contain a message sent on the chat. Let’s start off with the script:

<script>
export default {
name: 'Message',
props: [
'text', // Content of the message
'author', // Author of the message
'dark' // Background variant of the box
]
}
</script>

Here we only need to add the props property: The props, similiar to React, are those properties which we want to take as input from a parent component. This can be either an array or object, in case you wanted to specify the type of each prop.

Let’s then proceed with the template.

<template>
<div
:class='["message", { dark }]'
>
<h5>{{ author }}</h5>
{{ text }}
</div>
</template>

As previously said, on top of standard HTML we have some extras. Here’s what’s going on in this template:

  • We interpolate dynamic plain text with double brackets, both for the author name and the content of the message.
  • We use the directive v-bind (abbreviation is : symbol) to bind the class of the outer div. This can take both a string, array, or object, with mixes between those.

For the style, we write the following:

<style scoped>
.message {
background: #e7e7e7;
border-radius: 10px;
padding: 1rem;
width: fit-content;
}

.message.dark {
background: #e9eaf6;
}

h5 {
margin: 0 0 .5rem 0;
}
</style>

ChatBox.vue

This is the input box the user will write a new message with. Let’s start with the script:

<script>
export default {
name: 'ChatBox',
data: () => ({

// To keep the state of the input content
text: ''
}),
methods: {

// We will call this when the form is submitted
onSubmit(event) {

// This fires an event which we will handle
// in the parent component
this.$emit("submit", event, this.text);
this.text = '';
}
}
}
</script>

Here we have two other properties added: data and methods which are pretty self explanatory. Let’s proceed with the template.

<template>
<form class='chat-box' @submit='onSubmit($event)'>
<input
v-model='text'
placeholder='Write a message'
type='text'
/>
<button :disabled='text === ""'>Send</button>
</form>
</template>

Here we take a first look at two other directives:

  • v-on (abbreviation is @ symbol): We use it to listen to DOM events, in this case, the submit event on the form. Inside of it we call the method we previously created.
  • v-model: We use this to have a ready-to-use two-way data binding. You pass the data property and you’re good to go! We use it on the input element.

Finally, the style.

<style scoped>
.chat-box {
width: 100%;
display: flex;
}

input {
width: min(100%, 20rem);
flex-grow: 1;
}

button:disabled {
opacity: 0.5;
}
</style>

App.vue

This is the root component where we will handle all of the logic. Let’s start off with the script.

<script>
...imports...

export default {
name: 'App',
// Here we register the components which
// we are going to use in the template
components: {
RegisterDialog,
ChatBox,
Message
},
// This is going to be called
// when the component gets rendered
created() {
this.getChat();
},
methods: {
onRegister(event, name) {
event.preventDefault();
// Authentication is out of scope for this project
// so we just generate a uuid
this.user = { name, id: uid() };
},
getChat() {
listenChat((chat) => {
this.messages = chat.reverse().map(m => ({
...m,
isMine: m.uid && m.uid === this.user?.id
}));
});
},
// This method will be called when a new message is sent
onSubmit(event, text) {
event.preventDefault();

sendMessage({
text,
uid: this.user?.id,
author: this.user?.name
});
}
},
data: () => ({
user: undefined,
messages: []
})
}
</script>

Let’s take a look at the template now.

<template>
<div class='app'>
<div class='messages'>
<Message
v-for='message in messages'
:key='message.id'
:class='["message", { right: message.isMine }]'
:dark='message.isMine'
:text='message.text'
:author='message.author'
/>
</div>

<ChatBox
class='chat-box'
@submit='onSubmit'
/>

<RegisterDialog
v-if='!user'
@submit='onRegister'
/>
</div>
</template>

Here we plug in the components we previously coded (RegisterDialog was kept out for brevity). We can see there are two other directives introduced:

  • v-for: We use it to iterate the Message component on the messages array. We also add the :key attribute so that Vue.js identifies every item, which is why it needs to be unique.
  • v-if: We use it to conditionally render an element. In this case, the RegisterDialog. If the value passed is truthy, it will be rendered.

Finally, the style.

<style>
@font-face {
font-family: 'Georama';
src: url('./assets/Georama.ttf');
}

@font-face {
font-family: 'Georama';
src: url('./assets/Georama.ttf');
font-weight: bold;
}

* {
box-sizing: border-box;
}

html {
font-family: 'Georama', sans-serif;
}

body {
margin: 0;
}

button {
border: 0;
background: #2a60ff;
color: white;
cursor: pointer;
padding: 1rem;
}

input {
border: 0;
padding: 1rem;
background: rgba(0, 0, 0, 0.1);
}
</style>

<style scoped>
.app {
height: 100vh;
display: flex;
flex-direction: column;
}

.messages {
flex-grow: 1;
overflow: auto;
padding: 1rem;
}

.message + .message {
margin-top: 1rem;
}

.message.right {
margin-left: auto;
}
</style>

As you can see we are able to have two styles in the same component, one global and one scoped.

Conclusions

That’s it! 😎 A few things were left out in this article but you can take a look at the full source code here. Hopefully you were able to grasp some of the fundamentals of Vue.js, but there’s much more out there! ✨

--

--