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

Question about SubscribedBy method #120

Closed
debuggingmyhead opened this issue Sep 25, 2018 · 10 comments
Closed

Question about SubscribedBy method #120

debuggingmyhead opened this issue Sep 25, 2018 · 10 comments

Comments

@debuggingmyhead
Copy link

I'm currently creating a simple user management page and am using the Customer Form example (from Elements) to help guide me.

Similar to the example, I have a form that will be populated based on the current selected User in the DataGrid.

In my OnSubVMCreated method, I was going to use the following code (similar to the example):

_selectedUserId.SubscribedBy(
      subVM.User as ReactiveProperty<User>,
       id => _userService.Get(id))

However, my repository methods are async, which I know doesn't work in SubscribeTo/SubscribedBy. So instead I was going to use something like:

_selectedUserId.Subscribe(async id => await ...)

But then it occurred to me that I don't actually understand what is happening with SubscribedBy, so I don't know how to recreate what it is doing.

I looked through your code and it seems SubscribedBy calls subscriber.SubscribeTo(mapper(property)), which eventually calls property.Subscribe(subscriber), if I'm understanding it correctly.

What I don't understand is, using the original example, how is "id => _userService.Get(id)" getting called every time _selectedUserId changes? I'm sure I'm missing something and/or not understanding the Reactive stuff, but to me it looks like the end result of SubscribedBy is just having the ReactiveProperty observing the changes to _selectedUserId.

I guess my main questions are:

  1. How does the mapper function in SubscribedBy get called every time? Or if it doesn't, then in the Customer Form example, how is the applicable Customer entity getting pulled on selection change?

  2. Is there a way to recreate what it's doing but in an asynchronous manner?

  3. Should I instead just have the SubVM subscribe to an Event on the parent VM?

Sorry for the wall of text and hopefully my question makes sense. I'm having a hard time wrapping my head around this and any help is appreciated.

@dsuryd
Copy link
Owner

dsuryd commented Sep 26, 2018

It may help to look at it in terms of the original Rx APIs:

Publisher_TypeA.SubscribedBy(Subscriber_TypeB, TypeA_To_B_Mapper)  

// is equivalent to

var Publisher_TypeB = Publisher_TypeA.Select(A => Type_A_To_B_Mapper(A));
Publisher_TypeB.Subscribe(Subscriber_TypeB);

So the mapper gets called every time Publisher_TypeA emits a value.

Since it is an async get, new data it produces must be pushed manually, so we'll have to break the chain apart and write it like this:

_selectedUserId.Subscribe(async id => 
{
   var user = await _userService.GetAsync(id);
   (subVM.User as ReactiveProperty<User>).Value  = user;
   PushUpdates();
});

Hope this helps!

@debuggingmyhead
Copy link
Author

Ah, that does make a bit more sense but I definitely need to learn more about the observer pattern.

So, your solution did work (thank you!) but there is a bug which is either something with my code or the async nature of the lambda. The form field updates but it is sometimes one selection behind.

For example, the first selection doesn't update the form (but in the debugger I can see that the proper entity from the database is being set on the subVM), but then my second selection updates the field with the first selection's entity.

What's odd is that stepping through with the debugger, I can see the correct current selection getting set on the subVM and then PushUpdates getting called, but the actual value that is pushed to the React component is the previous selection's entity...

However, I can sometimes get it to update the form with the actual selection depending on how fast/slow I click around on the DataGrid, but I don't notice any pattern to it. Do you think it has to do with the async call?

@dsuryd
Copy link
Owner

dsuryd commented Sep 26, 2018 via email

@debuggingmyhead
Copy link
Author

Well, it seems the problem is indeed with async.

If I use a non-async lambda and use .Result on my repository method like you suggested, it works exactly as it should (i.e. the form updates with the correct selection, no matter how fast or randomly I click around the grid).

I suppose I should dig around and see if there is another way to implement async with Subscribe.

On a related note, would you recommend maybe not using async methods for my repositories? It seems like it would simplify things when using DotNetify.

Admittedly, I only set everything up as async because when I was learning MVC I kept seeing all the examples set up that way (and also I kept reading that when dealing with the database I should be awaiting async methods).

@dsuryd
Copy link
Owner

dsuryd commented Sep 26, 2018

Async won't hog the thread if it's doing expensive operations like DB access, so definitely keep it. Do check the browser console log if the right value is being sent. I'll test async with the form example in case the bug is dotNetify's, and will let you know.

@debuggingmyhead
Copy link
Author

Thanks, appreciate the help!

I did enable dotnetify.debug and when it's async, I can see that it sends the correct selected id # to the ViewModel but then sends the previous selection's object back (except for the times when it syncs properly).

@dsuryd
Copy link
Owner

dsuryd commented Sep 29, 2018

I was able to reproduce your issue and got to the bottom of the problem: the PushUpdates call after the async operation will only work if the view model that calls it has any change. But since the changes happen on the sub view models and not the master, then the updates won't be sent, unless those sub-VMs call PushUpdates too.

The best solution for this is to add a new property in the master VM to process the async operation, so that there will be data change on the master VM when the async call returns a value. Then get the sub VMs subscribe to this property:

OnSubVMCreated()
{
   if (_selectedUser == null)
   {
      var update = AddProperty<bool>("Update");
      _selectedUser = AddInternalProperty<User>("User");
      _selectedUserId.Subscribe(async id => 
      {
         _selectedUser.Value = await _userService.GetAsync(id);
         update.Value = true;
         PushUpdates();
      });
   }

   (subVM.User as ReactiveProperty<User>).SubscribeTo(_selectedUser);

@dsuryd
Copy link
Owner

dsuryd commented Sep 30, 2018

With the just-published Nuget v3.1, dotNetify now supports async reactive methods SubscribeToAsync and SubscribedByAsync, as well as adding new PushUpdates overload to force other view models on the same connection to update even though the calling view model doesn't have one.

The above can now be written like this:

constructor()
{
    _selectedUser = AddInternalProperty("User", default(User))
         .SubscribeToAsync(_selectedUserId, async id => await _userService.GetAsync(id));
}

OnSubVMCreated()
{
   (subVM.User as ReactiveProperty<User>)
      .SubscribeTo(_selectedUser)
      .Subscribe(_ => PushUpdates(true));
}

@dsuryd dsuryd closed this as completed Sep 30, 2018
@Spongman
Copy link

.Subscribe(_ => PushUpdates(true));

where is this Subscribe method defined?

@dsuryd
Copy link
Owner

dsuryd commented Jan 19, 2019

That’s from System.Reactive namespace.

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

3 participants