Waiting for controllers (or controller interaction)

One solution would be leveraging DOM events (which I’m generally a big fan of for cross-controller communication) and also introduce a request event fired by the DoStuffController in case it’s late to the game.

The HeadController would emit an event indicating that it has completed it’s workflow, e.g.

// HeadController.js
let event = new CustomEvent('pageInitialized', {detail: {additionalData: 'foo'} });
this.element.dispatchEvent(event);

DoStuffController will hear this unless HeadController finishes before DoStuffController has even initialized. DoStuffController, not knowing whether it’s late to the party, will always issue a request for the state, e.g.

// DoStuffController.js
initialize() {
  window.addEventListener('pageInitialized', this.handlePageInitialization, true);
  let event = new CustomEvent('pageInitializedQuery');
  this.element.dispatchEvent(event);
}

HeadController has already registered to listen for the pageInitializedQuery event and upon hearing it fires off the pageInitialized event if it’s ready to do so. Otherwise, it does nothing until it’s done doing its thing, at which point it fires off the pageInitialized event as per usual.

One thing to note is that dispatching events in this way is synchronous, unlike typical browser-generated events which leverage the event loop and are asynchronous. That means that by the time this.element.dispatchEvent(event) returns in DoStuffController, HeadController will have already dispatched its own event (if ready) and called DoStuffControllers handler for pageInitialized.

This should alleviate both the coupling and timing concerns. One gotcha is to make sure any listeners of HeadController handle its events with idempotency, as it’s possible they’ll hear the pageInitialized event twice. That could be done by keeping state within DoStuffController or even by removing the pageIntitialized event listener in DoStuffController after it’s triggered for the first time.

Hope this helps!