Render Components from Turbo Broadcasts
I was surprised to learn there is no built-in way to render components from turbo broadcasts.
Components (Phlex or ViewComponent) still feel very underutilized in Rails. Perhaps that will change in the future, but we made 20 years without them, so perhaps not.
Either way, I wanted an easy way to re-use them when broadcasting changes.
The easiest option I could find was something that looked like this:
def broadcast_created
component = Component.new(self)
html = ApplicationController.render(component, layout: false)
broadcast_append_later_to(
"the_stream",
target: "the_target",
html:
)
end
This works fine for a one-off. I could always add something like .as_html
to my base component class to make it less repetitive. But if I wanted to make the "component" just work, I added the following as an initializer in my Rails app.
module TurboStreamsBroadcastExtensions
def broadcast_action_later_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
render_component_to_html(rendering)
super
end
def broadcast_action_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
render_component_to_html(rendering)
super
end
private
def render_component_to_html(rendering)
if rendering&.dig(:component)&.respond_to?(:render_in) && rendering&.dig(:html).blank?
rendered_component = ApplicationController.render(rendering.delete(:component), layout: false)
rendering[:html] = rendered_component
end
end
end
Turbo::Streams::Broadcasts.prepend TurboStreamsBroadcastExtensions
With this in place, I can simply add "component" to my broadcast_append_later_to
def broadcast_created
broadcast_append_later_to(
"the_stream",
target: "the_target",
component: Component.new(self)
)
end
Perhaps this will be built someday, but for now, I think it will do the trick.
If you want to try this, here is a gist with the code.