Stimulus Discourse

Injector controller: pattern for modals


Hey all, wondering if anyone could give me a sanity check on an approach I’ve been spiking out.

Context: Rails/Turbolinks enabled

Situation: when a user clicks on a button, I want to show a modal that renders content.

I found two approaches that I wasn’t really happy with:

  • Render a hidden (with css) modal in my Rails templates directly, create a Stimulus controller to show/hide when a button fires an action – I dislike this approach because I don’t like shoving extra stuff into the DOM, imagine if you had 3-4 different modals you might need to show from a given page, maybe the modal needs to make different/extra queries to get content
  • Try using some third-party javascript library (bootstrap-modal, tingle, etc) and wrap it with Stimulus – seems overkill (I don’t really need a whole library for a modal) and I don’t know of an easy way to pass in the modal content

A third approach is one I tried and wanted some input on: an injector controller.

Globally register the whole body for this controller and have the button trigger an action and store some arguments in dataset

<body data-controller="injector"> 
  <button data-action="injector#inject" data-component="modal" data-url="/posts/new" />

Then the injector controller manages injecting components into the DOM – these components can and probably should be other Stimulus components

import { Controller } from "stimulus";

export default class extends Controller {
  connect() {
    this.injectedComponents = [];

      () => {
      { once: true }

  inject(event) {
    const data = event.currentTarget.dataset;
    const node = document.createElement("div");

    switch (data.component) {
      case "modal":
        node.innerHTML = `
          <div class="modal">
            <div class="modal-content">
              <div data-controller="content-loader"

      // repeat for other 'dynamic' components as necessary?
        throw ("Unknown injector component: ", data.component);

  teardown() {
    this.injectedComponents.forEach(i => {

(Note: content-loader is from the docs:

Any feedback? Too much indirection? Too much magic? Am I not thinking in the “Stimulus mindset” and doing something really dumb?



You said, that you are using Rails. In that case I would probably go with Server-generated JavaScript Response (SJR) + Stimulus. You can render the modal in the backend, inject it into the DOM with SJR and use Stimulus to manage it. Something like the following.

Use rails-ujs to create an Ajax link:

# app/views/users/index.html.erb

<%= link_to "New User", new_user_path, remote: true %>
# app/controllers/users_controller.rb

class UsersController < ApplicationController
  def new
    @user =

Insert your modal into the DOM:

# app/views/users/new.js.erb

document.body.insertAdjacentHTML("beforeend", "<%= j render "modal" %>");
# app/views/users/_modal.html.erb

<div data-controller="modal">

Shameless plug: I’ve wrote some articles about SJR, you can check them here: Also, please let me know if you have any questions regarding this method.


Ah – thanks! I did forget about SJR as an option, that does seem to fit the bill.


hey hey hey!

I happen to have written a gem that solves this exact problem! It uses SJR. And it doesn’t have any dependencies. It doesn’t use Stimulus because Stimulus wasn’t a thing then.

With it, you could to write an SJR response such as

Popup("<%=j render 'modal' %>").show("up")

to display your modal partial in a modal dialog.

I hope you’ll find it useful, @swanson



I agree that your modal should be requested when needed and rendered on the server to take advantage of ActionView.

But … I never liked using JQuery in any server response. Specifically I think of modals as html views and not javascript views. And the client framework should know how and where to render those html views (like a browser does with a full page load). See below for an example.

# app/views/widgets/index.html.erb

<%= link_to "New Widget", new_widget_path, rel: "modal:ajax" %>
# app/controllers/widgets_controller.rb

def new
  @widget =
  render layout: "modal"
# app/assets/javascripts/modals/ajax.js

$(document).ready(function() {
  $(document).on("click", "*[rel='modal:ajax']", function(event) {
      url: $(this).attr('href'),
      success: function(modalHTML, textStatus, jqXHR) {

I also like this approach because it gives me more client-side control over loading/error/other states when requesting a modal from the server … and it looks and acts similar to how I would implement this as a stimulus controller.