One of the core goals of every MVVM library is to provide an implementation of
ICommand. This interface represents one of the two major parts of what
constitutes a ViewModel - Properties and Commands. In keeping with the goal of
MVVM + Rx, ReactiveUI provides its own implementation of ICommand called
ReactiveCommand, that works a bit differently than most other
implementations.
Commands represent discrete actions that are taken in the UI - "Copy", "Open", and "Ok" are good examples of Commands. Usually these Commands are bound to a control that is built to handle Commands, like a Button. Cocoa represents this concept via the Target Action Framework.
Many Commands are invoked directly by the user, but some operations are also useful to model via Commands despite being primarily invoked progmatically. For example, many code paths involving periodically loading or refreshing resources (i.e. "LoadTweets") can be modeled well using Commands.
Since the act of invoking a Command represents an Event, ReactiveCommand
itself is an IObservable<object>. The object that is passed along via the
IObservable is the command parameter given to the Execute method of
ICommand:
var command = ReactiveCommand.Create();
command.Subscribe(x => this.Log().Info("The number is {0}", x));
command.Execute(4);
>>> The number is 4While ReactiveCommand supports the Command parameter, it is recommended to not
use it, and simply always pass null to the Execute method. Instead of using
the parameter, you should be using properties that are on the ViewModel.
Since ReactiveCommand is an Observable, all of the Rx operators can be used with it. Here are some practical examples:
// Note: This is for illustration purposes, see the Asynchronous
// ReactiveCommand chapter for a better way to do this
LoadTweets
.Where(_ => IsLoggedIn == true)
.SelectMany(async x => await FetchTweets())
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(x => LoadedTweets = x);
// Refresh when either the Command is invoked *or* the window is activated
shouldRefreshTweets = Observable.Merge(
this.Events().ActivatedObs.Select(_ => Unit.Default),
this.WhenAnyObservable(x => x.ViewModel.Refresh).Select(_ => Unit.Default));
shouldRefreshTweets
.Where(x => this.ViewModel != null)
.Subscribe(_ => ViewModel.RefreshData());All of the Commands we've created so far, can always be executed - their
CanExecute simply returns 'true'. To specify when a Command can be executed,
instead of using a Func<object, bool>, we'll use an IObservable<bool>.
Because we're describing not only whether a Command can be executed, but when
that value changes, we'll also get the implementation of CanExecuteChanged
for free.
Note that the parameter to CanExecute is ignored in ReactiveCommand. This is
because it is fundamentally incompatible with the notion of
CanExecuteChanged - if CanExecute(bar) is true and CanExecute(baz) is
false, when should we fire CanExecuteChanged?
The simplest thing we can possibly do to pass along CanExecute information, is
to use a Subject<bool>, which is an Observable that you control yourself by
hand. Here's how it works:
var commandCanExecute = new Subject<bool>();
var command = ReactiveCommand.Create(commandCanExecute);
commandCanExecute.OnNext(false);
command.CanExecute(null);
>>> false
commandCanExecute.OnNext(true);
command.CanExecute(null);
>>> trueWhile a Subject might be the easiest thing to understand, it certainly isn't
the most effective. Oftentimes, a far more appropriate CanExecute is one
that is based on other properties on the ViewModel. Since we want to be
notified when a Property changes, we use the WhenAny method, and we Select
it into a boolean value. For example:
// Whether we can post a Tweet, is based on whether the user has typed any
// text and whether it is short enough.
PostTweet = ReactiveCommand.Create(
this.WhenAnyValue(x => x.TweetContents)
.Select(x => !String.IsNullOrWhitespace(x) && x.Length < 140));
// You can often leave off the extra Select by using the selector built into
// WhenAny
OkButton = ReactiveCommand.Create(
this.WhenAny(x => x.Red, x => x.Green, x => x.Blue,
(r,g,b) => r.Value != null && g.Value != null && b.Value != null));Nearly all of your Commands will use this pattern to define when they can be executed. Since your Commands will be tied to Properties, many validation-type tasks can be accomplished in this way.
Unlike traditional ICommand implementations, ReactiveCommands can have as
many people listening to the Executed signal as you want. This is very
useful for decoupling, as the View can now listen to the ViewModels and
execute View specific code, such as setting control focus or scroll positions.
One may be tempted to simply write
ViewModel.SomeCommand.Subscribe(x => ...), but this code fails whenever the
ViewModel changes - you will be subscribed to the wrong command and it will
appear to never fire. A method called WhenAnyObservable solves this for you:
// Instead of doing this wrong code:
this.ViewModel.ClearMessageText
.Subscribe(x => MessageTextBox.GetFocus());
// Do this instead, which will handle null and changing ViewModels
this.WhenAnyObservable(x => x.ViewModel.ClearMessageText)
.Subscribe(x => MessageTextBox.GetFocus());One thing that is sometimes useful, is to create a Command which simply
invokes several other commands. ReactiveCommand helps you with this, via the
CreateCombined method. The advantage to using this method, is that the
CanExecute of the new Command will reflect the and of the child commands
(i.e. if any of the child commands can't be invoked, the parent can't be
either). This is especially useful when one of the commands has an
asynchronous action attached to it.
RefreshUsers.Subscribe(_ => this.Log().Info("Refreshing Users!"));
RefreshLists.Subscribe(_ => this.Log().Info("Refreshing Lists!"));
RefreshAll = ReactiveCommand.CreateCombined(
RefreshUsers, RefreshLists);
RefreshAll.Execute(null);
>>> Refreshing Users!
>>> Refreshing Lists!There are a few convenience methods built into the framework for invoking
commands. Any Observable can be used as a signal to invoke a command via
InvokeCommand:
// Invoke the Close command whenever the user hits escape. This will
// automatically do the CanExecute check for us before calling Execute.
this.Events().KeyUpObs
.Where(x => x.EventArgs.Key == Key.Escape)
.InvokeCommand(this, x => x.ViewModel.Close);Another convenience method called ToCommand allows you to create commands
directly from IObservable<bool>. The above CanExecute examples could be
more tersely written:
PostTweet = this.WhenAny(x => x.TweetContents,
x => !String.IsNullOrWhitespace(x.Value) && x.Value.Length < 140)
.ToCommand();
OkButton = this.WhenAny(x => x.Red, x => x.Green, x => x.Blue,
(r,g,b) => r.Value != null && g.Value != null && b.Value != null)
.ToCommand();