Outlets
by Julian Rubisch
Inter-process communication has been a source of many band-aids in the past. Luckily with outlets we now have an official way to invoke logic across controller boundaries.
Good
<body
data-controller=“job-dashboard”
data-job-dashboard-job-outlet=“.job”
>
<button data-action=“job-dashboard#refresh”></button>
<ul>
<li data-controller=“job” class=“job”></li>
</ul>
</body>
<body data-controller="job-dashboard" data-job-dashboard-job-outlet=".job" > <button data-action="job-dashboard#refresh"></button> <ul> <li data-controller="job" class="job"></li> </ul> </body>
// job_dashboard_controller.js
import { Controller } from ‘@hotwired/stimulus’;
export default class extends Controller {
static outlets = [‘job’];
refresh() {
this.jobOutlets.forEach((outlet) => {
outlet.update({…})
});
}
}
// job_controller.js
import { Controller } from ‘@hotwired/stimulus’;
export default class extends Controller {
update(data) {
// …
}
}
// job_dashboard_controller.js import { Controller } from '@hotwired/stimulus'; export default class extends Controller { static outlets = ['job']; refresh() { this.jobOutlets.forEach((outlet) => { outlet.update({...}) }); } } // job_controller.js import { Controller } from '@hotwired/stimulus'; export default class extends Controller { update(data) { // ... } }
Rationale
You have split your controllers according to the Single Responsibility Principe, and now want to send messages from one to another. This can occur, for example, in data-rich complex UIs like dashboards, data tables, media elements etc.
Contraindications
Tracking outlets via their selectors in the HTML can be tedious. The markup can become bloated and confusing. So I’d advise to use it sparingly, and look into custom events as an alternative.