Reactive WebSockets with Phoenix Channels

@MichalZalecki

michalzalecki.com

woumedia.com

reactive

\rē-ˈak-tiv\

done in response to a problem or situation

 merriam-webster.com

Library doesn't matter

  • RxJS 4
  • RxJS 5
  • most.js
  • xstream
  • Kefir
  • Bacon.js
  • whatever...

RxJS 5 (beta)

Rx.Observable.fromEvent(channel, "counter_changed")

most.js

most.fromEvent("counter_change", channel)

// Uncaught Error: source must support
// addEventListener/removeEventListener
// or addListener/removeListener

most.js

import * as most from "most";
import mostCreate from "@most/create";

mostCreate(add => channel.on("counter_changed", add));

Phoenix Channels

defmodule PhoenixChannels.RoomChannel do
  use Phoenix.Channel

  def join("rooms:lobby", _message, socket) do
    {:ok, socket}
  end

  def join("rooms:" <> _private_room_id, _params, _socket) do
    {:error, %{reason: "unauthorized"}}
  end

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

  def handle_out("new_msg", payload, socket) do
    push socket, "new_msg", payload
    {:noreply, socket}
  end
end

Non-reactive way

const channel = socket.channel("rooms:lobby", {});
const chatInput = document.querySelector("#chat-input");
const messagesContainer = document.querySelector("#messages");

chatInput.addEventListener("keypress", event => {
  if (event.code === "Enter") {
    channel.push("new_msg", { body: event.target.value });
    chatInput.value = "";
  }
});

channel.on("new_msg", payload => {
  const li = document.createElement("li");
  li.textContent = payload.body;
  messagesContainer.appendChild(li);
});

Reactive way

const channel = socket.channel("rooms:lobby", {});
const chatInput = document.querySelector("#chat-input");
const messagesContainer = document.querySelector("#messages");

const newMessage$ = Rx.Observable.fromEvent(chatInput, "change")
  .map(e => e.target.value);

newMessage$.subscribe(body => channel.push("new_msg", { body }));
newMessage$.subscribe(() => chatInput.value = "");

Rx.Observable.fromEvent(channel, "new_msg")
  .pluck("body")
  .scan((akk, msg) => `${akk}<li>${msg}</li>`, "")
  .subscribe(msgs => messagesContainer.innerHTML = msgs);

deck

By Michał Załęcki

deck

  • 1,228