【问题标题】:RX: how to pass the latest value from observable into ReactiveCommandRX:如何将 observable 中的最新值传递给 ReactiveCommand
【发布时间】:2017-03-28 14:18:08
【问题描述】:

我想获取IdStream 的最新值并在命令Execute 操作中使用它。

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);

我曾尝试使用答案 https://stackoverflow.com/a/31168822/7779560 并得到这样的结果:

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

它可以工作,但在这种情况下我不能使用 IsExecuting 和 ThrownExceptions 命令的功能(它们仅适用于我在命令创建期间作为 Execute 传递的空操作)。

更新:

执行顺序:

  1. IdStream正在创建
  2. 命令创建
  3. documentStream 处理 DocumentOpened 事件(获取一些 Id 值 - 我检查过)
  4. saveCommand执行

我怎样才能实现它?

UPD 2:我还需要等待命令体内的方法(例如SaveAsync)。

【问题讨论】:

    标签: c# .net reactive-programming observable reactiveui


    【解决方案1】:

    这对你有用吗?重播将保留最新发布的值。执行该命令时,它将获取最新的值,然后 Take(1) 取消订阅,因为您只需要一个值,然后将其推送到 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");
        }
    

    【讨论】:

    • 不幸的是,没有(我尝试了所有 DoSubscribe 方法组合,有和没有 Take(1))。我已经更新了描述,可能会提供更多信息。请注意,当Execute 启动时(如果不明显),IdStream 已经具有必要的值(不应等待它)。
    • 好吧,我在一个更大的示例中进行了编辑,只是将其更改为使用重播。我也错过了语法和 ReactiveCommand。您想使用 CreateFromObservable 以便它启动您传入的 Observable
    • 感谢您的帮助,您的示例适用于同步调用,但是异步调用呢?如何在 Do() 中等待 SaveAsync()?我尝试过类似的方法,但这似乎是一种解决方法,不是吗? var id = await connectedIdStream.Take(1).GetAwaiter(); await saveFileService.SaveAsync(id.Value);你能提出更好的解决方案吗?
    • 所以大约有 10 种不同的方法可以实现这一点。我更新了几个,但我认为通过introtorx.com阅读你会获得很多好处
    【解决方案2】:

    你想用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");
        }
    

    (我用 XUnit 重写了,因为我手头有它)

    这是一个稍微简化和扩展的测试用例,您的一些代码替换为我推荐的代码。

        [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");
        }
    

    【讨论】:

    • 看起来不像 ReactiveCommand 的 IsExecutingThrownExceptions 属性将在此实现中按预期运行。
    【解决方案3】:

    您可以将流的最新值存储在 ObservableAsPropertyHelper&lt;&gt; 属性中,并在您的命令中使用它。

    您的类级别属性如下所示:

    public IObservable<Option<Guid>> IdStream { get; }
    
    private ObservableAsPropertyHelper<Option<Guid>> _currentId;
    public Option<Guid> CurrentId => _currentId.Value;
    

    你的构造函数会像这样连接起来:

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

    您可能希望为CurrentId 属性提供默认值。您可以在ToProperty() 电话中执行此操作。

    【讨论】:

    • 这是最简单的方法,但有没有一种简单的方法可以避免显式状态管理,只需将 Observable 输入到 ReactiveCommand 中?
    • 我不知道。因为在创建命令之前,您不会获得命令的IObservable。您需要的是 CombineLatestIdStream 和按钮单击事件。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-04
    • 1970-01-01
    • 2019-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多