Isnor Creative Blog
Ruby on Rails, Ember, Elm, Phoenix, Elixir, React

Embr.js connection to a Phoenix channel

Update – Sept 19, 2016

There’s now an Ember addon – ember-phoenix – that aims to provide integration between Ember and Phoenix. In my most recent project I used it, found it helpful, and would encourage you to take a look.

Phoenix Channels

Phoenix channels have been the source of a fair bit of excitement in the web development community recently – and I think they’ve been a key factor in the growing interest in both Phoenix and the Elixir language. My interest was piqued – and if I’m honest, I might look back and say that my interest in Ruby on Rails peaked – when DHH announced at RailsConf 2015 in Atlanta recently that he’d tried to copy the feature for Rails’ new Action Cable.

Real time updates

Real time is not new to web apps; I recall developing chat room functionality for a Rails application many years ago using Juggernaut. But perhaps the combination of a growing client requirement for real time updates, and the developer-friendly implementation that Phoenix features has meant that its time has come.

Caveat Emptor

Myself, I’m actively learning the basics of channels in Phoenix, and am still fairly new to Ember. Thus I’m in no way claiming this to be any kind of authoritative method of putting Phoenix and Ember channels together. That said, this seemed very easily accomplished and is working for me so far.

There are a variety of Phoenix channel-related Ember addons and articles out there that you may want to look at as well:

Software Versions I’m using here:

  • Phoenix 1.0.1
  • Ember-cli 1.38.8
  • Ember 2.3.0-beta.2
  • Ember Data 2.2.1

Ok let’s go…

Phoenix

First I generated a channel in Phoenix:

mix phoenix.gen.channel Room rooms

Then I followed Phoenix’s instruction to add the channel to my Phoenix socket file in web/channels/user_socket.ex:

channel "rooms:lobby", Foo.RoomChannel

Ember Route

I generated a route in the Ember app using ember-cli

ember g route test

Ember Util

Next I generated an Ember util file. The rationale here – and if this sounds foolish to you, let me know – is based on my understanding that Ember does not Babelize ES6 files in vendor/ – but it will transpile everything under app/ :

ember g util phoenix

Into the util file I placed the contents of phoenix.js, copied verbatim from my Phoenix application (deps/phoenix/web/static/js/phoenix.js

Ember Content Security Policy

Next up I added my Phoenix server to my Ember apps content security policy in config/environment.js:

contentSecurityPolicy: {
  'connect-src': "'self' ws://localhost:4000"
}

Ember Phoenix Service

I then generated a Phoenix service:

ember g service phoenix

I fleshed out app/service/phoenix.js with this:

import Ember from 'ember';

import {Socket} from "../utils/phoenix";

export default Ember.Service.extend({
  socket: function () {
    let s = new Socket("ws://localhost:4000/socket");
    s.connect();
    return s;
  }
});

Ember Phoenix Initializer

And then I generated an Ember initializer:

ember g initializer phoenix

I then added some code to it in app/initializers/phoenix.js

export function initialize(application) {  
  application.inject('controller', 'phoenix', 'service:phoenix');
};
export default {
  name: 'socket',
  initialize: initialize
};

Ember Controller

And here’s my app/test/controller.js controller file:

import Ember from 'ember';
export default Ember.Controller.extend({  
  messages: [],
  channel: null,
  init: function() {
    let socket = this.get('phoenix').socket();  
    let room = socket.channel("rooms:lobby", {});
    this.set('channel', room);
    room.join().receive("ok", () => {
      console.log("Welcome to Phoenix Chat!");
    });
    room.on( "new:message", msg => this.renderMessage(msg) )
  },
  renderMessage: function (msg) {
    this.messages.pushObject(msg.body);
  },
  actions: {
    sendMessage: function (event) {
      let val = this.get('newMessage');
      this.get('channel').push("new:message", {body: val})
      this.set('newMessage', null);
      return false;
      event.preventDefault();
    }
  }
});

Handlebars Template

And my app/test/template.hbs file:

<div class="header">Channel Testing</div>
<div class="page">
  <div class="content-padded">
    {{#each messages as |message|}}
      {{message}}
    {{/each}}
    <div class="form-control">
      <label>New Message</label>
      {{input value=newMessage class="form-control"}}
      <button class="btn" {{action 'sendMessage'}}>Send Message</button>
    </div>
  </div>
</div>
<div class="footer">
</div>

This is all fairly rudimentary stuff but does serve to demonstrate how easily Ember can interface with a Phoenix channel.

Gordon B. Isnor writes about Ruby on Rails, Ember.js, Elm, Elixir, Phoenix, React, and the web. If you enjoyed this article, join his newsletter.

comments powered by Disqus