I'm trying use ReactiveUI 7.4 in WPF project, and I think it's great framework. But it causes great difficulties in studying the absence, or the outdated documentation.
In doc https://docs.reactiveui.net/en/user-guide/commands/asynchronous-synchronous.html says so CreateFromObservable is asynchrony, but in my example it's run syncronly.
RefreshList = ReactiveCommand.CreateFromObservable<ReactiveList<ClientDto>>(
() => Observable
.Return(_clientsService.GetClientsList())
and latter
_isBusy = this.WhenAnyObservable(x => x.RefreshList.IsExecuting)
.ToProperty(this, vm => vm.IsBusy);
when i do InvokeCommand method runs syncronly, and IsExecuting observe only after GetClientsList() completed (change to false and after to true). But variand with task works:
RefreshList = ReactiveCommand.CreateFromTask(async _ =>
{
return await Task.Run(() => _clientsService.GetClientsList());
}
);
Is it bug? Or changes in framework?
PS I also trying plays with SubscribeOn and ObservableOn but nothing helps (((.
Observable.Return() does it's work on the current thread, which means it's blocking. In your case the current thread is the UI thread.
You can specify a scheduler, but that only affects where the value is returned, not where it's produced.
I've written about Observable.Return() and how it behaves in this blog post.
It looks like _clientsService.GetClientsList() is implemented synchronously. To make it asynchronous you can move the work to the task pool. You've already done this by running it in a Task. It's also possible to use `Observable.Start()ยด:
RefreshList = ReactiveCommand.CreateFromObservable<ReactiveList<ClientDto>>(
() => Observable
.Start(_clientsService.GetClientsList(), RxApp.TaskpoolScheduler);
Related
I have tried to unsubscribe within the subscribe method. It seems like it works, I haven't found an example on the internet that you can do it this way.
I know that there are many other possibilities to unsubscribe the method or to limit it with pipes. Please do not suggest any other solution, but answer why you shouldn't do that or is it a possible way ?
example:
let localSubscription = someObservable.subscribe(result => {
this.result = result;
if (localSubscription && someStatement) {
localSubscription.unsubscribe();
}
});
The problem
Sometimes the pattern you used above will work and sometimes it won't. Here are two examples, you can try to run them yourself. One will throw an error and the other will not.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Something similar:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
This time you don't get an error, but you also unsubscribed before the 5 was emitted from the source observable of(1,2,3,4,5)
Hidden Constraints
If you're familiar with Schedulers in RxJS, you might immediately be able to spot the extra hidden information that allows one example to work while the other doesn't.
delay (Even a delay of 0 milliseconds) returns an Observable that uses an asynchronous scheduler. This means, in effect, that the current block of code will finish execution before the delayed observable has a chance to emit.
This guarantees that in a single-threaded environment (like the Javascript runtime found in browsers currently) your subscription has been initialized.
The Solutions
1. Keep a fragile codebase
One possible solution is to just ignore common wisdom and continue to use this pattern for unsubscribing. To do so, you and anyone on your team that might use your code for reference or might someday need to maintain your code must take on the extra cognitive load of remembering which observable use the correct scheduler.
Changing how an observable transforms data in one part of your application may cause unexpected errors in every part of the application that relies on this data being supplied by an asynchronous scheduler.
For example: code that runs fine when querying a server may break when synchronously returned a cashed result. What seems like an optimization, now wreaks havoc in your codebase. When this sort of error appears, the source can be rather difficult to track down.
Finally, if ever browsers (or you're running code in Node.js) start to support multi-threaded environments, your code will either have to make do without that enhancement or be re-written.
2. Making "unsubscribe inside subscription callback" a safe pattern
Idiomatic RxJS code tries to be schedular agnostic wherever possible.
Here is how you might use the pattern above without worrying about which scheduler an observable is using. This is effectively scheduler agnostic, though it likely complicates a rather simple task much more than it needs to.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
This lets you use a "unsubscribe inside a subscription" pattern safely. This will always work regardless of the scheduler and would continue to work if (for example) you put your code in a multi-threaded environment (The delay example above may break, but this will not).
3. RxJS Operators
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
The examples above re-written:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
It's working method, but RxJS mainly recommend use async pipe in Angular. That's the perfect solution. In your example you assign result to the object property and that's not a good practice.
If you use your variable in the template, then just use async pipe. If you don't, just make it observable in that way:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
And then you can use your result$ in cases when you need it: in other observable or template.
Also you can use pipe(take(1)) or pipe(first()) for unsubscribing. There are also some other pipe methods allowing you unsubscribe without additional code.
There are various ways of unsubscribing data:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = result;
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = result;
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = result;
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = result;
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = result;
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
I would personally prefer method 4, because you can use the same destroy subject for multiple subscriptions if you have in a single page.
If I have the following code:
const subject = new BehaviorSubject<[]>([]);
const observable = subject.asObservable();
subject.next([{color: 'blue'}])
observable.pipe(first()).subscribe(v => console.log(v))
According to the docs:
If called with no arguments, first emits the first value of the source Observable, then completes....
Does this mean that the source observable(the BehaviorSubject in this case) completes and you can no longer use it? As in you can no longer call .next([...]) on it.
I'm trying to understand how can an observable complete if it doesnt have the .complete() method on it?
I was trying to look at the source code of first() which under the covers uses take() and in turn take() uses lift() so I was curious if somehow first operator returns a copy of the source observable(the subject) and completes that.
The source observable is not completing, what it completes is the subscription. You could have multiple subscriptions on your Observable source, in your case one BehaviorSubject.
subject.next([{color: 'blue'}])
subject.next([{color: 'red'}])
const subs1 = observable.pipe(first()).subscribe(v => console.log(v))
const subs2 = observable.subscribe(v => console.log(v))
In the example above you clearly see that the source is not completing, just the subscription.
I have created a Stackblitz if you want to try it: https://stackblitz.com/edit/rxjs-uv6h6i
Hope I got your point!
Cheers :)
I have the following epic I use in my application to handle api requests:
action$ => {
return action$.ofType(actions.requestType)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) => (
Observable.create((obs) => {
obs.next({ type: type, value: action.value, form: action.form });
})
.debounceTime(250)
.switchMap((iea) => (
Observable.ajax(ajaxPost(url(iea.value), body ? body(iea.value) : action.form))
.mergeMap(payload => {
return Observable.merge(
Observable.of(actions.success(payload)),
/* some other stuff */
);
})
.catch(payload => {
return [actions.failure(payload)];
})
))
))
.takeUntil(action$.filter((a) => (a.type === masterCancelAction))
.repeat();
};
Basically, any time I perform an api request, I dispatch a request action. If I dispatch another request quickly, the previous one is ignored using debounceTime. Additionally, the request can be cancelled using the masterCancelAction and when cancelled repeat() restarts the epic. This epic works as intended in all cases expect one.
The failure case occurs when a user uses the browser back during a request. In this case I fire the masterCancelAction to the request. However, on the same execution context as a result from the masterCancelAction, another request action dispatches to perform a new request on the same epic, but the api request does not occur (the console.log does occur though) as if there was no repeat(). In other cases where cancels occur, the next request is not invoked from the same execution context and it works fine, so it seems in this case my code does not give repeat a chance to restart the epic?
A dirty workaround I found was to use setTimeout(dispatch(action), 0) on the request that dispatches after the cancellation. This seems to allow repeat() to execute. I tried passing different schedulers into repeat, but that didn't seem to help. Also, attaching takeUntil and repeat into my inner switchMap solves the problem, but then other cases where my next request does not execute in the same call stack fail.
Is there a way I can solve this problem without using setTimeout? Maybe it is not a repeat related problem, but it seems to be the case.
Using rxjs 5.0.3 and redux-observable 0.14.1.
The issue is not 100% clear without something like a jsbin to see what you mean, but I do see some general issues that might help:
Anonymous Observable never completes
When creating a custom anonymous Observable it's important to call observer.complete() if you do indeed want it to complete. In most cases, not doing so will cause the subscription to be a memory leak and might also other strange behaviors
Observable.create((observer) => {
observer.next({ type: type, value: action.value, form: action.form });
observer.complete();
})
Observable.of would have been equivalent:
Observable.of({ type: type, value: action.value, form: action.form })
However, it's not clear why this was done as the values it emits are in captured in scope.
debounceTime in this case does not debounce, it delays
Since the anonymous observable it's applied to only ever emits a single item, debounceTime will act just as a regular .delay(250). I'm betting you intended instead to debounce actions.requestType actions, in which case you'd need to apply your debouncing outside the switchMap, after the action$.ofType(actions.requestType).
Observable.of accepts any number of arguments to emit
This is more of a "did you know?" rather than an issue, but I noticed you're merging your of and /* some other actions */ I assume would be other of observables merged in. Instead, you can just return a single of and pass the actions as arguments.
Observable.of(
actions.success(payload),
/* some other actions */
actions.someOtherOne(),
actions.etc()
);
Also, when you find yourself emitting multiple actions synchronously like this, consider whether your reducers should be listening for the same, single action instead of having two or more. Sometimes this wouldn't make sense as you want them to have completely unrelated actions, just something to keep in mind that people often forget--that all reducers receive all actions and so multiple reducers can change their state from the same action.
.takeUntil will stop the epic from listening for future actions
Placing the takeUntil on the top-level observable chain causes the epic to stop listening for action$.ofType(actions.requestType), which is why you added the .repeat() after. This might work in some cases, but it's inefficient and can cause other hard to realize bugs. Epics should be thought of instead as sort of like sidecar processes that usually "start up" with the app and then continue listening for a particular action until the app "shuts down" aka the user leaves the app. They aren't actually processes, it's just helpful to conceptually think of them this way as an abstraction.
So each time it matches its particular action it then most often will switchMap, mergeMap, concatMap, or exhaustMap into some side effect, like an ajax call. That inner observable chain is what you want to make cancellable. So you'd place your .takeUntil on it, at the appropriate place in the chain.
Summary
As mentioned, it's not clear what you intended to do and what the issue is, without a more complete example like a jsbin. But strictly based on the code provided, this is my guesstimate:
const someRequestEpic = action$ => {
return action$.ofType(actions.requestType)
.debounceTime(250)
.do(() => console.log('handled epic ' + actions.requestType))
.switchMap((action) =>
Observable.ajax(ajaxPost(url(action.value), body ? body(action.value) : action.form))
.takeUntil(action$.ofType(masterCancelAction))
.mergeMap(payload => {
return Observable.of(
actions.success(payload),
/* some other actions */
...etc
);
})
.catch(payload => Observable.of(
actions.failure(payload)
))
);
};
Check out the Cancellation page in the redux-observable docs.
If this is a bit confusing, I'd recommend digging a bit deeper into what Observables are and what an "operator" is and does so that it doesn't feel magical and where you should place an operator makes more sense.
Ben's post on Learning Observable by Building Observable is a good start.
I'm subscribed to the IsExecuting of a command:
LoginCommand.IsExecuting.Subscribe(x => Log("Logging in"));
and it works fine when my Command is invoked by InvokeCommand but when I call it by:
LoginCommand.Execute();
The IsExecuting observable is not triggered.
This works:
Observable.Start(() => { }).InvokeCommand(LoginCommand);
Does someone know why the IsExecuting property doesn't change when calling the Execute method? I'm trying to unit test the command so I thought this would be the best way to execute it from tests.
After the upgrade to ReactiveUI 7.0, the Execute() method changed. Right now it does not trigger the command immediately. Instead, it returns a cold IObservable to which you have to subscribe in order to make stuff happen.
LoginCommand.Execute().Subscribe();
Check in the write up about the changes in RxUI 7.0 in the release notes. Ctrl+F "ReactiveCommand is Better". It states explicitly:
the Execute exposed by ReactiveCommand is reactive (it returns IObservable). It is therefore lazy and won't do anything unless something subscribes to it.
When you want to execute an ReactiveCommand, you can do it like this:
RxApp.MainThreadScheduler.Schedule(Unit.Default, (scheduler, state) =>
ViewModel.MyCommand.Execute(state).Subscribe());
You can then Subscribe to it like this:
this.WhenActivated(d => {
MyCommand
.Select(_ => this)
.ObserveOn(RxApp.MainThreadScheduler)
.ExecuteOn(RxApp.TaskScheduler)
.Subscribe(viewModel => {
// ...
})
.DisposeWith(d);
});
I'm would like to know the recommended way to bind to ReactiveCommand's IsExecuting.
The problem is the initial command execution (started at the end of the constructor) is not updating the WPF control using IsLoading as a binding, although subsequent calls work as expected.
Update 2 Add test binding code
This shows the adorner content when IsLoading is true
<ac:AdornedControl IsAdornerVisible="{Binding IsLoading}">
<ac:AdornedControl.AdornerContent>
<controls1:LoadingAdornerContent/>
</ac:AdornedControl.AdornerContent>
<fluent:ComboBox
ItemsSource="{Binding Content, Mode=OneWay}"
DisplayMemberPath="Name"
SelectedValuePath="ContentId"
SelectedValue="{Binding SelectedContentId}"
IsSynchronizedWithCurrentItem="True"
/>
</ac:AdornedControl>
Update
I found this:
https://github.com/reactiveui/rxui-design-guidelines
and figured I should be able to do something like:
this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.ToProperty(this, x => x.IsLoading);
but it gives the compilation error:
The type arguments for method
'ReactiveUI.OAPHCreationHelperMixin.ToProperty<
TObj,TRet>(System.IObservable< TRet>, TObj,
System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet,
System.Reactive.Concurrency.IScheduler)' cannot be inferred from the
usage. Try specifying the type arguments explicitly.
I also tried:
this._isLoading = this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.ToProperty<TheViewModel, bool>(this, x => x.IsLoading);
but get the compilation error:
'System.IObservable< System.IObservable< bool >>' does not contain a
definition for 'ToProperty' and the best extension method overload
'ReactiveUI.OAPHCreationHelperMixin.ToProperty<
TObj,TRet>(System.IObservable< TRet>, TObj,
System.Linq.Expressions.Expression< System.Func< TObj,TRet>>, TRet,
System.Reactive.Concurrency.IScheduler)' has some invalid arguments
and
Instance argument: cannot convert from
'System.IObservable>' to
'System.IObservable'
Original Below
The code listed at the end of my post works for the initial bind by accessing the IsLoading property and it sounds like that kicks off a subscription. But from further reading it seems I should be using WhenAny and I can't seem to figure out what has been put in front of my nose:
ToProperty and BindTo - Get initial value without Subscribing
Adding:
this.WhenAnyValue(x => x.LoadCommand.IsExecuting);
also works, but is there a better way?
I was thinking removing the ObservableAsPropertyHelper as it doesn't seem to be doing much for me and making IsLoading a normal property like:
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set { this.RaiseAndSetIfChanged(ref _isLoading, value); }
}
And doing something like the following, but it doesn't compile because it is trying to assign a IObservable< bool> to a bool:
this.WhenAnyValue(x => x.LoadCommand.IsExecuting)
.Subscribe(x => IsLoading = x);
Current code:
private readonly ObservableAsPropertyHelper<bool> _isLoading;
public bool IsLoading
{
get { return _isLoading.Value; }
}
LoadCommand = ReactiveCommand.CreateAsyncTask(async _ =>
{
//go do command stuff like fetch data from a database
}
LoadCommand.IsExecuting.ToProperty(this, x => x.IsLoading, out _isLoading);
//works if I have this line
var startSubscription = IsLoading;
LoadCommand.ExecuteAsyncTask();
and figured I should be able to do something like:
You've got the right idea, but the syntax is a bit off, try:
this.LoadCommand.IsExecuting
.ToProperty(this, x => x.IsLoading, out _isLoading);
If you were to do this with objects that can change (i.e. you've got a long expression), there's a special method called WhenAnyObservable that you use instead of WhenAnyValue:
this.WhenAnyObservable(x => x.SomeObjectThatMightBeReplaced.IsExecuting)
.ToProperty(this, x => x.IsLoading, out _isLoading);
I have run into this before and I think what you are experiencing lies here.
ToProperty / OAPH changes
ObservableAsPropertyHelper no longer is itself an IObservable, use WhenAny to observe it.
ObservableAsPropertyHelper now lazily Subscribes to the source only when the Value is read for the first time. This significantly improves performance and memory usage, but at the cost of some "Why doesn't my test work??" confusion. If you find that your ToProperty "isn't working", this may be why.
It is lazy, so you must subscribe to it (i.e. request a value from the property if using OAPH) for it to work. That is why you notice that your var startSubscription = IsLoading; 'fixes' the issue.
Knowing that made it easier for me to determine whether or not this was even an issue, or just something to keep in mind during my unit tests, knowing that in my application these properties would be bound to and hence subscribed to, making it moot in practice. You know, the whole "tree falling in the forest with no one there to hear it" idea.
I think you should stick with the ToProperty that you have, that seems the way to go IMHO.