Building A Real-Time Chat Application With Vue.js and Firebase - Part 2 - YouTube

Building A Real-Time Chat Application With Vue.js and Firebase - Part 2 - YouTube

In the first part of the _Building A Real-Time Chat Application With Vue.js and Firebase_tutorial series we’ve set up the Vue.js project, installed needed dependencies like the Bootstrap framework, established the connection to Firebase and started with the implementation.

In this second part we’re going to further complete the implementation of the Vue application and we’re going to implement the Chat and CreateMessage component.

Implementing The Template

Let’s create a new file src/views/Chat.vue and start inserting the following template code first:

<template>
    <div class="chat container">
        <h2 class="text-primary text-center">Real-Time Chat</h2>
        <h5 class="text-secondary text-center">Powered by Vue.js & Firebase</h5>
        <div class="card">
            <div class="card-body">
                <p class="nomessages text-secondary" v-if="messages.length == 0">
                    [No messages yet!]
                </p>
                <div class="messages" v-chat-scroll="{always: false, smooth: true}">
                    <div v-for="message in messages" :key="message.id">
                        <span class="text-info">[{{ message.name }}]: </span>
                        <span>{{message.message}}</span>
                        <span class="text-secondary time">{{message.timestamp}}</span>
                    </div>
                </div>
            </div>
            <div class="card-action">
                <CreateMessage :name="name"/>
            </div>
        </div>
    </div>
</template>

This codes makes use of Bootstrap’s CSS classes. The output is depending on whether the messages array is filled with messages or not.

If no messages are available the text_ [No messages yet!]_ is presented to the user. If messages are available the chat messages are printed out by iterating over the _messages _array using the _v-for _directive. For each message the user name, the message text and the message timestamp are printed out.

To ensure that the user can always see the latest message we’re using the v-chat-scroll plugin for the div chat div container. To make this plugin available add the package by executing the following command within the project directory:

$ npm install vue-chat-scroll

and make sure that the plugin is activated for our application by adding the following two lines of code in main.js:

import VueChatScroll from 'vue-chat-scroll'

Vue.use(VueChatScroll)

Adding The Component’s JavaScript Code

To further complete the implementation of Chat.vue add the JavaScript code embedded in a <script>-element next:

<script>
    import CreateMessage from '@/components/CreateMessage';
    import fb from '@/firebase/init';
    import moment from 'moment';

    export default {
        name: 'Chat',
        props: ['name'],
        components: {
            CreateMessage
        },
        data() {
            return{
                messages: []
            }
        },
        created() {
            let ref = fb.collection('messages').orderBy('timestamp');

            ref.onSnapshot(snapshot => {
                snapshot.docChanges().forEach(change => {
                    if (change.type == 'added') {
                        let doc = change.doc;
                        this.messages.push({
                            id: doc.id,
                            name: doc.data().name,
                            message: doc.data().message,
                            timestamp: moment(doc.data().timestamp).format('LTS')
                        });
                    }
                });
            });
        }
    }
</script>

A few things to note here: First of all three import statement have been added.

  • The _CreateMessage _component (which is not implemented yet) is already imported because Chat component is using _CreateMessage _as a child component.
  • The _messages _array is declared as a data property of the component.
  • The created() method is a lifecycle hook and called after a component is created but before the component output is added to the DOM. In our case the created hook is used to access the Firestore _messages _collection, read out the data and make sure that all available messages are inserted into the component’s _messages _property, so that it can be accessed in the template code.

To access Firestore we’re making sure that we’re adding the following import statement on top:

import fb from '@/firebase/init';

As we’re exporting the Firestore reference in init.js we’re able to access Firestore via _fb _in Login component. A reference to the collection is created inside the _created _method by using:

let ref = fb.collection('messages').orderBy('timestamp');

To retrieve the data of that collection we need to use the _onSnapshot _method. By using that method you can listen to a document with the onSnapshot() method. An initial call using the callback you provide creates a document snapshot immediately with the current contents of the single document. Then, each time the contents change, another call updates the document snapshot.

Inside the callback function we’re checking for updated documents inside the messages collection by using snapshot.docChanges(). Iterating through the list of changed documents is done by using method forEach. Here we need to pass in another callback function which is invoked for every document inside that list of changed document.

Inside that second callback function we’re checking if the value of change.type is added. This is needed because the following logic should only be executed for new messages being added to the collection in Firestore. If this is the case we’re added this new message document to the component’s _messages _array, so that it is displayed to the user instantly.

Adding Styles

Finally, let’s add the styles section to Chat.vue:

<style>
.chat h2{
    font-size: 2.6em;
    margin-bottom: 0px;
}

.chat h5{
    margin-top: 0px;
    margin-bottom: 40px;
}

.chat span{
    font-size: 1.2em;
}

.chat .time{
    display: block;
    font-size: 0.7em;
}

.messages{
    max-height: 300px;
    overflow: auto;
}
</style>

Implementing CreateMessage Component

In the template code of _Chat _component we’ve already included the _CreateMessage _component by using the following line of code:

<CreateMessage :name="name"/>

Herewith we’re making sure that the value of the Chat _component’s _name _property (which is received from _Login Component) is passed to CreateMessage _component (again as _name _property) and that the output of _CreateMessage component is embedded in the output of _Chat _component.

To make that code work we need to add the implementation of _CreateMessage _component to the Vue.js project next.

Implementing The Template

Let’s add a new file src/components/CreateMessage.vue to the project and insert the following template code first:

<template>
    <div class="container" style="margin-bottom: 30px">
        <form @submit.prevent="createMessage">
            <div class="form-group">
                <input type="text" name="message" class="form-control" placeholder="Enter message ..." v-model="newMessage">
                <p class="text-danger" v-if="errorText">{{ errorText }}</p>
            </div>

            <button class="btn btn-primary" type="submit" name="action">Submit</button>
        </form>
    </div>
</template>

This template is containing the HTML code which is needed to output a simple form consisting of one input field. This field is of type text and bound to the _newMessage _property of the component. The submit event of this form is bound to the _createMessage _event handler method which we’re going to implement in the next step.

Let’s now add the following JavaScript code (embedded in a -tag) in CreateMessage.vue:

    <script>
    import fb from '@/firebase/init';

    export default {
        name: 'CreateMessage',
        props: ['name'],
        data(){
            return {
                newMessage: null,
                errorText: null
            }
        },
        methods: {
            createMessage () {
                if (this.newMessage) {
                    fb.collection('messages').add({
                        message: this.newMessage,
                        name: this.name,
                        timestamp: Date.now()
                    }).catch(err => {
                        console.log(err);
                    });
                    this.newMessage = null;
                    this.errorText = null;
                } else {
                    this.errorText = "A message must be entered!"
                }
            }
        }
    }
</script>

First of all we’re making sure that we have access to the Firestore reference by including the following import statement on top:

import fb from '@/firebase/init';

Two data properties are defined (newMessage and errorText) by using the data function. Furthermore adding the name property to the array which is assigned to the component’s props property. This is needed to be able to receive name as an input property.

Inside the methods object the createMessage method is implemented. Inside this method we’re checking first if a message has been entered (if this.newMessage has a value). If this is the case the new message object is inserted into the Firestore messages collection by calling fb.collection(‘messages’).add.

If no message value is available we’re setting errorText to the value “A message must be entered!”, so that this error message is displayed to the user.

Completing The Router Configuration

Finally we need to complete the router configuration in src/router.js:

    import Vue from 'vue'
import Router from 'vue-router'
import Login from './views/Login.vue'
import Chat from '@/views/Chat.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'Login',
      component: Login
    },
    {
      path: '/chat',
      name: 'Chat',
      component: Chat,
      props: true,
      beforeEnter: (to, from, next) => {
        if (to.params.name) {
          next();
        } else {
          next({name: 'Login'});
        }
      }
    }
  ]
})

The configuration for the /chat path is added and connected to the newly created Chat_component. In this route configuration the _props property is set to true. This is needed because we want to pass the name as a router property.

Furthermore we’re attaching a route guard function to the beforeEnter property. This function is executed before the route is accessed. The function is used to first check if the user has already performed the login process (a login name is available via to.params.name. If this is the case the next() function is called to conclude the routing process to /chat. In any other case the next method is used to route back to Login component.

What’s Next

In this second part of the _Building A Real-Time Chat Application With Vue.js and Firebase_tutorial series we’ve further completed the implementation of the Vue.js front-end application.

30s ad

VueJS V1 Introduction to VueJS JavaScript Framework

Getting started with Vuejs for development

Vuejs 2 + Vuex + Firebase + Cloud Firestore

Building Applications with VueJs, Vuex, VueRouter, and Nuxt

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

Brave, Chrome, Firefox, Opera or Edge: Which is Better and Faster?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

What are the differences between the various JavaScript frameworks? E.g. Vue.js, Angular.js, React.js

What are the differences? Do they each have specific use contexts?

Ember.js vs Vue.js - Which is JavaScript Framework Works Better for You

In this article we will discuss full details and comparison of both Ember.js and Vue.js