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:
- https://github.com/foxnewsnetwork/ember-phoenix-chan
- http://chrismcg.com/2015/07/04/teaching-ember-cli-to-talk-to-phoenix-sockets/
- https://github.com/BlakeWilliams/ember-phoenix-adapter
- https://github.com/mgamini/ember-phoenix-socket
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.
I am available for Ember and Elixir/Phoenix consulting work – get in touch to learn more.