Search code examples
ruby-on-railswebsocketactioncableruby-on-rails-7

Rails 7/Action Cable: Access consumer instance from view


On Rails 7, I have created a channel called "SessionChannel". When my page loads, the consumer is being properly created as I can see the log show in the browser.

This is my app/javascript/channels/session_channel.js:

import consumer from "channels/consumer"

var channel = consumer.subscriptions.create("SessionChannel", {                           
  connected() {
    console.log("Connected to session channel");                                    
    // Called when the subscription is ready for use on the server
  },

  disconnected() {
    // Called when the subscription has been terminated by the server
  },

  received(data) {
    console.log("got data: " + data);
    // Called when there's incoming data on the websocket for this channel
  }
});

setTimeout(() => {
  channel.send({ some_data_to_rails: "goes_here" })
  console.log("sent");
}, 3000);

This works fine and the channel.send properly sends data back up the channel. But it seems I can only access the channel variable inside this file. If I try to access it in the browser or the application.js for example, it's undefined.

The question: Is it possible to export this variable (consumer/channel instance) to the app-wide javascript scope? So that I can channel.send from any view? Or at least be able to do it in the browser?


Solution

  • You can access it from a browser console and from inline scripts if you add it to window:

    const channel = consumer.subscriptions.create("SessionChannel", {
      // ...
    });
    
    window.channel = channel;
    

    However, that's not necessary:

    // app/javascript/channels/session_channel.rb
    
    import consumer from "channels/consumer";
    
    // NOTE: make sure to export it
    export default consumer.subscriptions.create("SessionChannel", {
      received(data) {
        console.log("SessionChannel", data);
      },
    });
    
    # app/channels/session_channel.rb
    
    class SessionChannel < ApplicationCable::Channel
      def subscribed
        stream_from "session_channel"
      end
    
      def receive(data)
        ActionCable.server.broadcast "session_channel", data
      end
    end
    

    You can create a stimulus controller to make it "accessible from the veiw":

    // app/javascript/controllers/hello_controller.rb
    
    import { Controller } from "@hotwired/stimulus";
    
    // NOTE: now you have something to import
    //       you are working with modules, so `sessionChannel`
    //       will only be available in this file, if you need it
    //       somewhere else then you have to import it there as well.
    import sessionChannel from "channels/session_channel";
    
    export default class extends Controller {
      static targets = ["message"];
    
      send(event) {
        event.preventDefault();
        sessionChannel.send({ message: this.messageTarget.value });
        this.messageTarget.value = "";
      }
    }
    

    Use it in any view:

    <!-- app/views/home/index.html.erb -->
    
    <div data-controller="hello">
      <%= text_area_tag :message, "", data: { hello_target: "message" } %>
      <%= button_tag "say: hello", data: { action: "hello#send" } %>
    </div>
    

    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import