2

I want to get the latest value of the IdStream and use it in command Execute action.

public IObservable<Option<Guid>> IdStream { get; }

IdStream = documentStream.OfType<DocumentOpened().Select(x => x.Document.Id.Some())
.Merge(documentStream.OfType<DocumentClosed().Select(x => Option<Guid>.None()));

var saveCommand = ReactiveCommand.Create(() => Save(id), CanExecute);

I had tried to use the answer https://stackoverflow.com/a/31168822/7779560 and got something like this:

var saveCommand = ReactiveCommand.Create(() => { }, CanExecute);
saveCommand.WithLatestFrom(IdStream, (_, id) => id)
            .Subscribe(id => Save(id));

And it works, but I can't use IsExecuting and ThrownExceptions command's functionality in this case (they act only for empty action which I passed as Execute during command creation).

UPD:

Execution order:

  1. IdStream creating
  2. Command creating
  3. documentStream processes DocumentOpened event (get some Id value - I checked it)
  4. saveCommand execution

How can I achieve it?

UPD 2: I need also to await methods inside command body (SaveAsync, for example).

Community
  • 1
  • 1

3 Answers3

2

Does this work for you? Replay will retain the latest value published. When the command is executed it will grab the latest value, the Take(1) unsubscribes after that because you only need one value, and then it pushes that to the Save;

    [Test]
    public void ExecuteUsingLastProducedValue()
    {
        Subject<string> producer = new Subject<string>();
        IObservable<bool> CanExecute = Observable.Return(true);
        IObservable<string> IdStream = producer;
        string SaveCalledWith = String.Empty;

        Func<string, Task> SaveAsync = (id) =>
        {
            SaveCalledWith = id;
            return Task.Delay(0);
        };

        // IdStream creating
         var connectedIdStream =
            IdStream
            .Replay(1);

        connectedIdStream
            .Connect();

        //Command creating
        var command = ReactiveCommand.CreateFromObservable(() =>
        {
            return connectedIdStream
                .Take(1)
                .Do(async id =>
                {
                    await SaveAsync(id);
                });
        }
        , CanExecute);


        //Alternate way
        //Command creating
        //var command = ReactiveCommand.CreateFromObservable(() =>
        //{
        //    return connectedIdStream
        //        .Take(1)
        //        .SelectMany(id => SaveAsync(id).ToObservable());
        //}
        //, CanExecute);


        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        Assert.AreEqual(String.Empty, SaveCalledWith);

        //trigger execution of command
        command.Execute(Unit.Default).Subscribe();

        //Verified Saved Called With is called
        Assert.AreEqual(SaveCalledWith, "working");
    }
Shane Neuville
  • 2,127
  • 14
  • 19
  • Unfortunately, no (I tried all of `Do` and `Subscribe` methods combinations with and without `Take(1)`). I have updated the description, possibly, it would give some more information. Note, please, that IdStream already has a necessary value (shouldn't wait it) when `Execute` starts (if it wasn't obvious). – Anton Zhuraulevich Mar 29 '17 at 12:50
  • alright I edited in a bigger example and just changed it to use a Replay instead.. Also I had missed the syntax and ReactiveCommand. You want to use CreateFromObservable so that it initiates the Observable you pass in – Shane Neuville Mar 29 '17 at 16:29
  • Here are some good references http://www.introtorx.com/content/v1.0.10621.0/02_KeyTypes.html#ReplaySubject http://www.introtorx.com/content/v1.0.10621.0/14_HotAndColdObservables.html#PublishLast – Shane Neuville Mar 29 '17 at 17:42
  • Thank you for your help, your example works fine for synchronous calls, but what about asynchronous calls? How can I await SaveAsync() in Do()? I have tried something like this, but it seems like a some workaround, isn't it? `var id = await connectedIdStream.Take(1).GetAwaiter(); await saveFileService.SaveAsync(id.Value);` Can you propose the better solution, please? – Anton Zhuraulevich Mar 30 '17 at 13:42
  • So there's about 10 different ways to achieve that.. I updated with a couple but I think you would get a lot of mileage out of reading through http://www.introtorx.com/ – Shane Neuville Mar 30 '17 at 20:24
0

You want to use Observable.Sample

    [Fact]
    public void ExecuteUsingLastProducedValue()
    {
        Subject<string> producer = new Subject<string>();
        IObservable<bool> CanExecute = Observable.Return(true);
        IObservable<string> IdStream = producer;
        string SaveCalledWith = String.Empty;

        Func<string, Task> SaveAsync = (id) =>
        {
            SaveCalledWith = id;
            return Task.Delay(0);
        };

        // IdStream creating
        var connectedIdStream =
            IdStream
                .Replay(1);

        connectedIdStream
            .Connect();

        //Command creating
        var command = ReactiveCommand.Create(() => { } , CanExecute);
        connectedIdStream.Sample( command )
                         .Subscribe( id => SaveAsync(id) );

        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        SaveCalledWith.Should().Be( String.Empty );

        //trigger execution of command
        command.Execute(Unit.Default).Subscribe();

        //Verified Saved Called With is called
        SaveCalledWith.Should().Be( "working");
    }

( I rewrote with XUnit because I had that on hand )

Here is a slightly simplified and extended test case with some of your code replaced by what I would recommend.

    [Fact]
    public void ExecuteUsingLastProducedValue()
    {
        var producer = new Subject<string>();
        var canExecute = Observable.Return(true);
        var saveCalledWith = String.Empty;

        void Save(string id) => saveCalledWith = id;

        var rcommand = ReactiveCommand.Create(() => { } , canExecute);

        // When cast to ICommand ReactiveCommand has a
        // more convienient Execute method. No need
        // to Subscribe.
        var command = (ICommand) rcommand; 


        producer
            .Sample( rcommand )
            .Subscribe( Save );

        //documentStream processes DocumentOpened event (get some Id value - I checked it)
        producer.OnNext("something random");
        producer.OnNext("working");

        //At this point Save still hasen't been called so just verifiyng it's still empty
        saveCalledWith.Should().Be( String.Empty );

        //trigger execution of command
        command.Execute( Unit.Default );

        //Verified Saved Called With is called
        saveCalledWith.Should().Be( "working");

        command.Execute( Unit.Default );

        saveCalledWith.Should().Be( "working");

        producer.OnNext("cat");
        saveCalledWith.Should().Be( "working");
        command.Execute( Unit.Default );
        saveCalledWith.Should().Be( "cat");
        producer.OnNext("dog");
        saveCalledWith.Should().Be( "cat");
        command.Execute( Unit.Default );
        saveCalledWith.Should().Be( "dog");
    }
bradgonesurfing
  • 30,949
  • 17
  • 114
  • 217
  • Doesn't look like ReactiveCommand's `IsExecuting` and `ThrownExceptions` properties will behave as expected in this implementation. – Tony Apr 27 '17 at 20:54
0

You can store the most recent value of your stream in an ObservableAsPropertyHelper<> property and use it in your command.

Your class level properties would look like this:

public IObservable<Option<Guid>> IdStream { get; }

private ObservableAsPropertyHelper<Option<Guid>> _currentId;
public Option<Guid> CurrentId => _currentId.Value;

And your constructor would wire things up like this:

IdStream.ToProperty(this, x => x.CurrentId, out _currentId);
var saveCommand = ReactiveCommand.Create(() => Save(CurrentId), CanExecute);

You might want to provide a default value for the CurrentId property. You can do that in the ToProperty() call.

Kris McGinnes
  • 372
  • 2
  • 7
  • That's the easy way, but is there a simple way to avoid explicit state management and just feed an Observable into ReactiveCommand? – Tony Apr 27 '17 at 20:59
  • Not that I know of. Because you're not given the `IObservable` of the command until after it's created. What you need is to `CombineLatest` with the `IdStream` and the button click event. – Kris McGinnes Apr 28 '17 at 21:58