Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to send message between components inside one app? #270

Closed
madmaxio opened this issue Jun 5, 2018 · 11 comments
Closed

How to send message between components inside one app? #270

madmaxio opened this issue Jun 5, 2018 · 11 comments

Comments

@madmaxio
Copy link

madmaxio commented Jun 5, 2018

In the two apps example, there are two activators which are used for sending messages.
So if I understand correctly all components must share main app Msg type? Or there is other way?

@hgzimmerman
Copy link
Member

It should also be possible to have your root component pass a callback to one of its children via props, which when activated, would cause the main component to change some state that another one of its children rely upon, causing a state change in that child. This works OK in some situations, but fails to be ergonomic when you have a deeply nested component structure, or if you want to neatly encapsulate state within components, and not pass everything via props.

Alternatively, you could set up your own custom service that is attached to the Context and that holds onto a callback. When you instantiate the component that would be receiving the messages, you would register a callback with the service. Then, when you want to send the message from another component, you would call context.yourservice.send(YourServiceMsg::Variant), causing the service to pass that message variant to the receiving component via the registered callback (callback.emit()).

The latter would likely contain some code that looks like the following:

Receiving component

fn create(_: Self::Properties, context: &mut Env<Context, Self>) -> Self {
    ...
    let callback = context.send_back(
        |service_msg: YourServiceMsg| {
            Msg::from(service_msg)
        },
    );
    context.your_service.register_cb(callback);
    ...
}

Sending component

fn update(&mut self, msg: Msg, context: &mut Env<Context, Self>) -> ShouldRender {
    match msg {
        Msg::SendMessageToOtherComponent => {
            context.your_service.send(YourServiceMsg::Variant)
        }
    }
    false
}

Your Service

pub struct YourService {
    // This could be a Collection type if you wanted to support multiple receiving components,
    // but this would cause a memory leak if you didn't find a solution to remove
    // the callback from the service when the component is destructed.
    // Maybe returning a Task from the register_cb fn that when it drops, the service would delete the corresponding callback?
    callback: Option<Callback<YourServiceMsg>> 
}
pub enum YourServiceMsg {
    ...
}
impl YourService {
    fn send(&mut self, msg: YourServiceMsg) {
        if let Some(cb) = self.callback {
            cb.emit(msg)
        }
    }
    fn register_cb(&mut self, callback: Callback<YourServiceMsg>) {
        self.callback = Some(callback)
    }
}

@madmaxio
Copy link
Author

madmaxio commented Jun 5, 2018

Great post, thank you! I will try test this.

@hgzimmerman
Copy link
Member

hgzimmerman commented Jun 5, 2018

Before you go ahead and implement my suggestion, I would like you to know that the typical arrangement for message passing between parent and child components is defined in my first paragraph. The example I typed up should only be used for the rather rare edge case where the components that need to communicate sit far apart in the view "tree".

So if you just have a component that is the direct child of another component, go with my first suggestion. I'm on a phone at the moment, but I imagine that at least one of the examples demonstrates this parent<->child message passing and I'll try to edit my post to link to it later.

EDIT:
The custom components example shows off the typical child to parent communication.

https://github.com/DenisKolodin/yew/blob/master/examples/custom_components/src/lib.rs#L69 The main Model instansiates a Button and creates a callback that performs an action in the parent.

https://github.com/DenisKolodin/yew/blob/master/examples/custom_components/src/button.rs#L15 The Button component accepts a callback.
If the values passed to the Button as props changed in the Model, the change() method will be called for the button, allowing you to define how the Button component would change in response.

https://github.com/DenisKolodin/yew/blob/master/examples/custom_components/src/button.rs#L42 When the <button> element is clicked, the Button will emit a value to the callback, causing the update() method to be called in the main Model.

bors bot added a commit that referenced this issue Jun 16, 2018
272: Multi-threading, concurrency, agents r=DenisKolodin a=DenisKolodin

This is a series of bold experiments and I really love 💓 this PR.
It makes this framework a **multi-threaded** (it's not a joke) and brings actors model everywhere.
Now your yew frontend-apps will be more _Erlang_ or _Actix_ apps like 🚀

Also, I've removed a context. Completely! Components simplified. Now it's an actor which you could connect to and interact with messages.
Other benefit is your components could interact each other #270

Since this PR will be merged the framework turned into multi-threaded concurrency-friendly frontend framework. Sorry me for buzzwords overload )

It still need Routing #187 and fixes of the most issues. I'll get to that.
But extra benefit of this PR: it fixes major emscripten issues #220

Remaining:

- [x] Add CHANGELOG.md
- [x] Update README.md
- [x] Create issue: Send `Connected` notification for `Private` agents (#282)
- [x] Create issue: Send `Connected` notification for `Public` agents (#282)
- [x] Create issue: Implement `Global` kind of agents (based on `SharedWorker`) (#283)
- [x] Create issue: Add components interaction example (#284)

Co-authored-by: Denis Kolodin <deniskolodin@gmail.com>
@madmaxio
Copy link
Author

Cross component message passing works great now!

Thank you for this amazing work! I think Yew is best frontend experience so far at the moment, even compared to most popular js/ts frameworks.

Still I've got a couple more question:

Inside the worker, does some api exist to see who is connected to the bridge?

Idiomatic way to send inner messages just inside one component is the agent way also ? (or activator or similar thing should be used?)

@hgzimmerman
Copy link
Member

hgzimmerman commented Jun 26, 2018

To sort of answer your first question:
As far as I'm aware, there isn't an API to see what other components are connected to an agent, but you can implement something like that yourself.

You can see that fn handle(&mut self, msg: Self::Input, who: HandlerId) takes a HandlerId as one of its parameters. The HandlerId is just a wrapper around a usize if I remember correctly, so you can't determine what the original component was.
What you can do, is save the HandlerIds when a component connects. Then, for some inputs, you could broadcast to all components instead of just responding to the one provided for you in handle().
I did this recently in a routing library I'm writing: here

If you wanted to get fancy with it, you could categorize the IDs into distinct sets by sending a message to the agent that tells it to move it out of the subscriptions hashset and into a more specific one or Option slot. It would conceptually be prone to programmer error (you may forget to send the categorization message after creating the bridge), but you could use this pattern to accomplish seeing what is connected at any time.

I don't fully understand your second question. For intra-component communication, you just use the Component::Message. If you want to chain or send multiple messages to the Component's reducer (update method), one of your messages can wrap another message in either a Box or Vec respectively.
Example here, although I expect this example to change in the near future to no longer use this pattern

@madmaxio
Copy link
Author

madmaxio commented Jun 27, 2018

For intra-component communication, you just use the Component::Message.

True, but how to send it just from the code? For example, what I want to do is:

Msg::Foo => {
let data = 1 + 2;
something_sending_inner_message(Msg::Bar(data));
}

@hgzimmerman
Copy link
Member

I think what you want is something like:

    fn update(&mut self, msg: Self::Message) -> ShouldRender {
        match msg {
            Msg::Foo => {
                let data = 1 + 2;
                self.update(Msg::Bar(data));
                false
            }
            Msg::Bar(_data) => {
                // Do something with data
                true
            }
        }
    }

@madmaxio
Copy link
Author

AH, I thought calling update is not really valid option! Thx!

@austinvhuang
Copy link

I'm finding this discussion helpful 3 years on.

@hgzimmerman what happened to the custom_components example? I'd like to have a look, but it seems to have disappeared leaving only the pub_sub example.

@Magicloud
Copy link

Sorry post in this closed issue. I wonder if there is already a built in way to do this task. Is it Scope?

@madmaxio
Copy link
Author

madmaxio commented Jun 14, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants