Stimulusには他のコントローラーを取得し、そのコントローラーの関数を実行することができます。これまで使ったことのない機能だったのでどういう状況で使えそうか試してみた。
責務の分離例
基本的には責務を分離しているときに使えそうです。逆にいうと分離せず、同じコントローラーで書くこともできますが、結果的に責務が分離されず肥大しているコントローラーはよくあるので、それを防ぐことができそうです。
例えばTimerController
というタイマーを扱うコントローラーとBoardController
というゲームのボードを扱うコントローラーがあるとします。
下の処理では、ゲームを開始したときに、タイマーを自動で始動させるようにしています。TimerController#start
はBoardController
に実装することもできますが、ボードとタイマーのそれぞれの役割を分離しています。
// app/javascript/controllers/timer_controller.js
import { Controller } from "@hotwired/stimulus"
export default class TimerController extends Controller {
// existing code ...
start() {
if (this.isRunning) return;
this.isRunning = true;
this.intervalId = setInterval(() => {
this.totalSeconds++;
this.updateDisplay();
}, 1000);
this.updateButtonStates();
}
// existing code ...
}
// app/javascript/controllers/board_controller.js
import { Controller } from "@hotwired/stimulus"
export default class BoardController extends Controller {
static targets = ["timer"]
// existing code ...
processNumberInput(value) {
this.startTimer();
// existing code ...
}
startTimer() {
const timerController = this.application.getControllerForElementAndIdentifier(this.timerTarget, "timer")
timerController.start();
}
}
共通化例
「Formのリクエスト方法の検討」をしたときに、Ghost Form パターン
がありました。Ghost From、いわゆる画面に表示させないが、非表示のエレメントをStimulusからリクエストするというものでした。
詳しくは『サーバ側だけでフォーム画面をインタラクティブに! 〜Hotwire を活用した「Ghost Formパターン」〜』
import { Controller } from "@hotwired/stimulus"
// Connects to data-controller="ghost-form"
export default class extends Controller {
static targets = [ "originalForm", "ghostForm" ]
submit(event) {
const formData = new FormData(this.originalFormTarget)
formData.delete("_method")
formData.delete("authenticity_token")
for (const [key, value] of formData.entries()) {
const ghost_key = "ghost_" + key
const input = this.ghostFormTarget.querySelector(`input[name="${ghost_key}"]`)
if (input) {
input.value = value;
}
}
this.ghostFormTarget.requestSubmit()
}
}
*『サーバ側だけでフォーム画面をインタラクティブに! 〜Hotwire を活用した「Ghost Formパターン」〜』より引用
このような実装例になっており、Ghost Form パターンを利用するケースではどこでも共通化できるようになっています。
こういった場合でも、ghost_form_controller
を他のコントローラーから参照するようにすれば、共通化ができそうです。
// app/javascript/controllers/board_controller.js
import { Controller } from "@hotwired/stimulus"
export default class BoardController extends Controller {
static targets = ["timer", "ghostForm"]
// existing code ...
processNumberInput(value) {
this.startTimerIfExists();
// existing code ...
this.submitGhostForm();
}
startTimer() {
const timerController = this.application.getControllerForElementAndIdentifier(this.timerTarget, "timer")
timerController.start();
}
submitGhostForm() {
const ghostFormController = this.application.getControllerForElementAndIdentifier(this.ghostFormTarget, "ghost_form")
ghostFormController.submit();
}
}
まとめ
責務を分離することを主とした例と共通化することを主とした例をそれぞれ見てきました。責務の分離、共通化することで、コントローラーの肥大化を防ぎ、メンテナンス性の向上が期待できそうです。