Description
Discussed in https://github.com/orgs/Relm4/discussions/644
Originally posted by chriskilding May 28, 2024
Imagine you have the following Components, as part of a wider application:
App
: your top-level component with the app windowDocument
: a headless component, just responsible for saving thingsContent
: a content sub-viewSidebar
: a sidebar sub-view
At the moment, if Content
or Sidebar
raise any events that you'd like to save to the Document
, you'd have to relay all those events through the App
's events. In other words you have to create AppInput
events that mirror all of the Content
and Sidebar
events.
As a result, the App
's events balloon to encompass everything you might want to send to (or between) the child views, which is not ideal.
Is there a better way to accomplish this?
What if the MessageBroker
(or something like it, such as a Router
) were adapted to provide the ability to forward events to multiple receivers. Something like this...
struct Document {}
enum DocumentInput {
SetFoo(u32)
}
impl Worker for Document {
// ...
}
struct Sidebar {}
enum SidebarInput {
SetFoo(u32)
}
impl Component for Sidebar {
view! { ... }
}
struct Content {}
enum ContentOutput {
Foo(u32)
}
impl Component for Content {
view! { ... }
}
struct App {
document: Controller<Document>,
sidebar: Controller<Sidebar>,
content: Controller<Content>,
}
enum AppInput {}
enum AppOutput {
Foo(u32)
}
impl Component for App {
view! { ... }
// broker is provided as part of the init context
fn init(_: Self::Init, root: Self::Root, sender: ComponentSender<Self>, broker: MessageBroker) -> ComponentParts<Self> {
// broker does all the forwarding, so we don't use `Connector#forward` etc any more
let content = Content::builder().launch(());
let sidebar = Sidebar::builder().launch(());
let document = Document::builder().launch(());
broker.forward(content.output_sender())
// can send to multiple inputs
.to(document.input_sender(), |msg| match msg {
ContentOutput::Foo(num) => DocumentInput::SetFoo(num)
})
.to(sidebar.input_sender(), |msg| match msg {
ContentOutput::Foo(num) => SidebarInput::SetFoo(num)
})
// can send to outputs
// (this can also be used to propagate the event upwards (if App had a parent component))
.to(sender.output_sender(), |msg| match msg {
ContentOutput::Foo(num) => AppOutput::Foo(num)
});
let model = App { content, sidebar, document };
// the rest of component initialisation
}
}
Side note: I don't exactly know where the broker
(or router
) object should be hosted, so initially I've made it one of the fn init
arguments / context. This also means its parent Component will have a persistent reference to it, which ensures the broker
object will stick around for the lifetime of the Component (which is what we want).