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 DoStuffController
s 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!