Chat web-app using Phoenix and Vue.js — Part 2

Let’s go ahead and have users enter their name, before they’re given access to the chatroom. That way we can actually see who’s chatting! :)

Here’s all the parts in this series:
Part 1 — Introduction and getting a basic web-app with chat functionality going
Part 2 (this article) — Make it possible for a user to identify themselves by name before joining the chat
Part 3 — See who’s online in the chat with you
Part 4 — Prettier design + fun css transitions

The code we’ll be adding in this tutorial can be seen here:

In the first part of this tutorial, we got a basic Phoenix app going and integrated Vue.js to use for the view layer. We could post messages using Phoenix channels and see them appear immediately in another browser window.

Let’s move things around

A lot is going on here, if you prefer to skip it you can see the changes here:

First we’ll do a bit of clean-up and re-arranging of our code from Part 1. We’ll take most of the socket connection logic we have in web/static/js/socket.js and move that in to our my-app component. So go ahead and open up socket.js

We’ll take everything in the mounted() function and move that to a new method in web/static/components/my-app.vue named connectToChat see the full methods: { .. } from my-app.vue here:

methods: {
sendMessage() {"new_msg", { body: this.message })
this.message = ''
  connectToChat() {
this.socket = new Socket("/socket", {params: {token: window.userToken}}),
this.socket.connect() ="room:lobby", {});"new_msg", payload => {
payload.received_at = Date();
.receive("ok", response => { console.log("Joined successfully", response) })
.receive("error", response => { console.log("Unable to join", response) })

As you might already have noticed, we instantiate a new socket in connectToChat() so because of that, we’ll go ahead and delete the let socket = .. and socket.connect() lines from socket.js

We need to add the data attributes to my-app.vue as well, so under the data() method add:

data() {
return {
socket: null,
channel: null,
messages: [],

Since this attributes now live locally in the my-app.vue , we don’t want to reference the $parent to access them anymore. So in sendMessage change this.$ to and delete the computed: { ... } altogether.

We can’t really use the Socket without importing it, so after the opening <script> in web/static/components/my-app.vue let’s add import {Socket} from "phoenix"

Let’s go ahead and move the initialization of our Vue.js app in to web/static/js/app.js it doesn’t really make sense that that lives in socket.js anymore. So we’ll now have a app.js that looks like this:

import "phoenix_html"
import Vue from 'vue'
import MyApp from "../components/my-app.vue"
// Create the main component
Vue.component('my-app', MyApp)
// And create the top-level view model:
new Vue({
el: '#app',
render(createElement) {
return createElement(MyApp, {})

Ok! Let’s make it so you have to give a name before entering the chat

What’s your name?

We want the behavior to be, that before you visit the chat you need to enter your name. We’ll keep it a bit simple and use Vue.js v-if and v-else .

We’ll add a div around our <ul>..</ul> and <input ...> in our my-app.vue — this is done so we can conditionally hide that portion of the template. Give the div a class attribute named messages :

<div class="messages">
<ul v-for="message in messages">
<small>{{message.received_at}}</small>: {{message.body}}
<input type="text" v-model="message" v-on:keyup.13="sendMessage">

Above it, we’ll add a new div with a class attribute named user-details . Let’s add that below our h1 tag:

<div class="user-details">
<label>Please enter your name:</label><br>
<input type="text" v-model="username">
<button v-on:click="connectToLobby">Next</button>

Just as we did with the message input field, I have added an attribute named v-model with the value "username" — so let’s go ahead and add that to the data() function, which now looks like:

data() {
return {
socket: null,
channel: null,
messages: [],
message: "",
username: ""

I also added a button, that uses the v-on:click to call a function when someone clicks it. It calls our connectToChat function that we added before.

If we start our server with mix phoenix.server and go to localhost:4000 we should now see our new input field. If we fill in a name in the field and hit the next button, we should see a message in the developer console:

That means that we have connected to the channel! Phew! Things are still working. We need to do a little bit of additional work to send the name to the channel as well.

In my-app.vue go to the connectToChat function, and remove the token: window.userToken (we don’t need to do any auth or verification of who you are) inside of the the params of the Socket initialization and reference the this.username instead:

this.socket = new Socket("/socket", {params: {username: this.username}}),

That means that we pass username to the socket as part of the params. We’re almost there, so let’s open up web/channels/user_socket.ex

Go ahead and change the connect function a little bit, and user Elixir’s pattern matching:

def connect(%{"username" => username}, socket) do
{:ok, socket}

That way we can capture the username that we pass in. You can go ahead and add IO.puts inspect username and try it out for yourself! :)

We just need to make one small adjustment, for us to be able to reference the username later on, let’s change {:ok, socket} to {:ok, assign(socket, :username, username)}

That will assign the :username to the socket. Let’s quickly make it so when you broadcast a new message, it shows up with your name on it. In web/channels/room_channel.ex add username: socket.assigns.username to the map for the broadcast! call:

def handle_in("new_msg", %{"body" => body}, socket) do
broadcast! socket, "new_msg", %{
body: body,
username: socket.assigns.username
{:noreply, socket}

This way we send out the username together with the message. There’s just one thing left to do — and you might already have guessed it? Exactly! Open up my-app.vue and go to the <template> — add {{message.username}} to the line that outputs the message when received:

<small>{{message.received_at}}</small>: <strong>{{message.username}}</strong>: {{message.body}}

The output now looks like:

That’s all fine. But we probably shouldn’t show both the name field and the message field at the same time. And waaaay higher up in this article, I talked about Vue.js conditionals — and then I completely dropped the ball. So let’s go ahead and wire those up now.

In my-app.vue for the <div class="user-details"> go ahead and add another attribute v-if="enterName" and down under data() { ... } add the enterName attribute:

data() {
socket: null,
channel: null,
messages: [],
message: "",
username: "",
enterName: true

At the top of connectToChat() add this.enterName = false . That way, when you connect to the socket, we set enterName to false and the “Please enter your name” section shouldn’t be visible anymore. But what about the chat section? That’s currently visible all the time. Vue.js has a neat trick up it’s sleeves — if we go ahead and add v-else to the messages-div:

<div class="messages" v-else>

Then Vue.js will do the opposite with this div, than the v-if does for the element just before it.

So let’s see what we got:

Pretty cool! Let’s make one small adjustment, so you can actually see what your name is before you type something. In my-app.vue in the template, inside the messages-div, just above the input field:

<input type="text" v-model="message" v-on:keyup.13="sendMessage">

That way the name will be visible! :)

Stay tuned for part 3, where we’ll add a list of users that are currently in the chat room!

The code can as always be found on github:

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.