

A Refreshing Tonic, Realtime Updates with Phoenix Channels
source link: http://engineering.teacherspayteachers.com/2017/05/23/a-refreshing-tonic-realtime-updates-with-phoenix-channels.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.


Engineering at Teachers Pay Teachers
Teachers Pay Teachers is the world's first and largest marketplace where teachers buy, sell, and share original educational materials. Our community of over 3 million educators has generated over $175 million in payouts to educators around the world.A Refreshing Tonic, Realtime Updates with Phoenix Channels
By Keyan Pishdadian, May 23, 2017
Over the last few months I’ve been working in Elixir and its most popular web framework Phoenix. During this time I built “Houston”, a deployment tool written in Elixir/Phoenix to help make deploying at TpT easier by providing a simple uniform interface to all of our Jenkins Pipelines. The Houston interface shows a number of important pieces of time-sensitive information that I needed to keep up-to-date so developers could coordinate deployments more effectively. I wanted to minimize the usage of other frameworks and tackle the problem quickly. I read about the team at thoughtbot using Phoenix Channels in lieu of React to deliver updated content to the browser and decided that using Channels would allow me to implement realtime updates easily by leveraging Phoenix directly.

A few weeks ago I had the pleasure of pairing with Chris McCord, one of the co-creators of the Phoenix framework, when TpT invited him to help with our migration to Elixir. One of the things we worked on together was implementing realtime updates for Houston with Channels. The Houston code is unfortunately still closed source, but I have created a simple application, TonicTime, to demonstrate the same Channel concepts. You can see the code on GitHub. The code on GitHub is slightly modified from the snippets here due to how it is being hosted.
After a short blurb about Channels we will look at the most important snippets from TonicTime to see how it all works.
What are Phoenix Channels?
Channels are a simple high-level abstraction for developing realtime components in Phoenix applications using WebSockets. Channels allow for interaction between application components that are not Elixir processes without building out a separate pub/sub system or using a third-party service like Pusher.
Using Channels
If you take a look at the embedded live demo of TonicTime below you will notice that the time is constantly updating without reloading the page or submitting a new request. So how is this happening? Below is a diagram of some of the components involved. We’ll look at the Javascript/HTML first and then the supporting Elixir modules. The lettered interaction stages are referenced below as we walk through the code.

The index template contains only one div
with the current time inside, web/templates/page/index.html.eex
:
<div id="clock" class="jumbotron">
<h2><%= @time_now %></h2>
</div>
Above @time_now
is accessing a variable passed to the template in the assigns
map. The Phoenix docs go into more detail about this, see Phoenix.View.render/3
.
Javascript
All of our Javascript is in web/static/js/app.js
, it opens a socket connection to our application’s mount point (A) and also specifies that we should replace the innerHTML content when receiving update messages (G):
// We set an explicit id on the div in our HTML to allow
// us to easily access it and replace its content.
let container = document.getElementById("clock")
// The code on GitHub connects to "/time/socket", this is due
// to how TonicTime is deployed. The socket endpoint just needs
// to match the socket created in the TonicTime.Endpoint module.
let socket = new Socket("/socket")
socket.connect()
let timeChannel = socket.channel("time:now")
// When an `update` message is received we replace the contents
// of the "clock" element with server-side rendered HTML.
timeChannel.on("update", ({html}) => container.innerHTML = html)
// Attempt to connect to the WebSocket (Channel).
timeChannel.join()
.receive("ok", resp => console.log("joined time channel", resp))
.receive("error", reason => console.log("failed to join", reason))
Elixir
There are a number of moving parts here:
- The socket mount-point (B) needs to be defined in our
Endpoint
setup. As mentioned above, this needs to match the socket endpoint that we attempt to connect to in our Javascript code:
socket "/socket", TonicTime.UserSocket
- The function
Page.Controller.index
which handlesGET
requests to the index:
def index(conn, _params) do
# Get the time from the TimeManager state, we'll look at this
# in detail below.
time_now = TimeManager.time_now()
# Render the template `index.html` passing `time_now` in
# the `assigns` map.
render conn, "index.html", [time_now: time_now]
end
- The
TimeChannel
Channel (C) which listens for updates and alerts subscribers:
defmodule TonicTime.TimeChannel do
use TonicTime.Web, :channel
# Client method called after an update.
def broadcast_update(time) do
# Render the template again using the new time.
html = Phoenix.View.render_to_string(
TonicTime.PageView,
"index.html",
[time_now: time]
)
# Send the updated HTML to subscribers.
TonicTime.Endpoint.broadcast(
"time:now",
"update",
%{html: html}
)
end
# Called in `app.js` to subscribe to the Channel.
def join("time:now", _params, socket) do
{:ok, socket}
end
end
- The
TimeManager
GenServer (D) which holds the current time in its state, and is also responsible for triggering updates to the time (E). If you look at the full file you’ll see there is a lot of code there to facilitate interaction with the GenServer. Most of this is not important to understanding Channels, the most relevant function is below:
defp update_time do
updated_time =
"US/Eastern"
|> Timex.now()
|> Timex.format!("%I:%M:%S %p", :strftime)
# Schedule another update call to happen in 1 second.
schedule_time_update()
# Send the updated to our Channel so it can update clients.
TonicTime.TimeChannel.broadcast_update(updated_time)
%{clock: updated_time}
end
Review
By leveraging Channels I was able to reduce the use of another framework in my project and still provide users with seamless dynamic page content. While we have looked at a trivial example of what Channels can do, there are many possibilities and native support in Phoenix makes implementation fast and natural.
Hopefully through this overview I was able to provide you with an easy to follow introduction to Channels and a framework for implementing them in your own projects.
Credits
A huge thanks to Chris McCord who walked me through Channels and their usage in Phoenix. Credit for the pun in the title, “A refreshing tonic”, goes to my fantastic friend and coworker Shanti Chellaram, who is almost as good as making up puns as she is at programming. And finally a big thanks to Ryan Sydnor for his help with building Houston, his many edits to this post, and his endless enthusiasm.
Recommend
-
23
tonic is a gRPC over HTTP/2 implementation focused on high performance, interoperability, and flexibility. It has been a few months since I originally...
-
32
Setting Up a gRPC Protobuf Server With Tonic #rust Apr 12...
-
6
Inverse Finance acquires Tonic Finance in possible first-ever DeFi protocol merger – HodlalertInverse Finance acquires Tonic Finance in possible first-ever DeFi protocol merger...
-
7
In this post, we will learn how to use Rust Tonic gRPC crate and implement CRUD with Postgresql database. The purpose of it is to...
-
23
Announcing Tonic 0.5We are pleased to announce version 0.5 of Tonic, a native gRPC implementation in Rust. 0.5 is a big release and has been in the works for...
-
1
Tonic (Component Framework) Chris Coyier on Sep 27, 2021 (Updated on Sep 29, 2021) Take your JavaScript to the next level at .
-
4
Combining Axum, Hyper, Tonic, and Tower for hybrid web/gRPC apps: Part 4 By Michael Snoyman, September 20, 2021 Share this
-
49
Share this This is the third of four posts in a series on combining web and gRPC services into a single service using Tower, Hyper, Axum, and Tonic. The full four parts are:
-
34
Share this This is the second of four posts in a series on combining web and gRPC services into a single service using Tower, Hyper, Axum, and Tonic. The full four parts are:...
-
5
Building a Realtime Chat App with Django Channels and WebSockets Building stateful web applications can be tricky, unless you use a framework, of course—Django to the rescue! I...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK