I'm novice to RxJS, I'm trying to implement chain of observable that behave exactly like MS-Excel. The concept: Lets assume the excel have 5 columns 'Name', 'Age', 'Sex', 'Country', 'Zipcode'. We can apply filter on each column independently that also affect the records show in the other columns.
Here the data-source receives the data back-end service, the data-source will have only two functions "addRecord" & "removeRecord".
How I'm trying to achieve here lets say I will create Observable and attach to the data-source call it as OBS-1 this will receive data from data-source. The OBS-1 can have its own filters. Lets say I will create another Observable OBS-2 which will receive data OBS-1 (filtered data if any filters in OBS-1). Another Observable say OBS-3 which again receive data from OBS-2 (filtered if any in OBS-2), so on.
If OBS-2 is destroyed (unsubscribed) the OBS-3 will receive the data from OBS-1.
How do we achieve this in the RxJs?
I think you misunderstood a few thing about Rx. Observables do not have filters and you do not 'live' add and remove filters from them. Neither do observables forward data based on who is subscribed.
Instead, you build up a call chain. You start with a source observable, like one from the addRecord and one from the removeRecord event. You then chain these observables to form new observables trough various operators in Rx and eventually you subscribe to the final observable. Subscribing will activate the entire chain and when the source events fire, all operators will trigger and eventually the event will (if not filtered) reach subscribe.
You can actually do the thing you describe in Rx. Changing a filter on an observable for example can be done relatively easy with switchMap, an operator that let you project a sequence onto another and switch over to the new sequence each time. For example filterSource.switchMap(filterFunction => Obs-1.filter(filterFunction)). Even simpler than this, you could just unsubscribe the first subscription and set up the Rx chain again. Using the build in functions however leaves a lot of juggling state out of the equation.
However, i strongly suspect you do not actually need behavior that is this complicated. What you want can be archived simply like this:
var Src-1 = fromEvent(dataSource, 'addRecord') // create the first source
var Src-2 = fromEvent(dataSource, 'removeRecord') // and the other source
var Obs-1 = Src-1.combineLatest(Src-2) // combine both sources
.filter(e => someCondition(e)) // filter the source
var Obs-2 = Obs-1.mergeMap(e => someOtherCondition(e) ? Change(e) : Rx.Observable.of(e)) // on someOtherCondition, either transform the source with the `Change(e)` function. Or keep it unchanged with `of(e)`
var Obs-3 = Obs-2.filter(e => anotherCondition(e)) // Filter again
var sub = Obs-3.subscribe() // activate the sequence.
Related
I'm going to phrase the question in rxjs, but I suppose it's similar for any Rx or observable library.
Say I have an observable of users, and a function getAssociates(user) that returns another observable of users. I want to use the getAssociates function on every user in the observable and return an observable of those associates. flatMap is enough for this.
But I also want to run getAssociates on each associate that comes back, but without ever running it twice on any given user (since two users might share an associate, and if A has B as an associate, then B also has A as an associate).
Something like the expand operator is what I think I'm looking for:
seedUsers.pipe(
expand(user => getAssociates(user)),
);
but how can I get in the bit about not running twice on any given user? I could maintain a list of seen users, but I'd like to achieve it in a functional style.
Conceptually, you need to :
Keep track of the known users, for example using a Set
Filter the known users before making a request, for example using filter operator
Here is a suggestion :
let knownIds = new Set();
getAllItems(Ids){
return from(Ids).pipe(
filter(id => ! knownIds.has(id)),
concatMap(id => getAllItems(id)),
map( id => knownIds.add(id))
)
}
getAllItems([originalId]).subscribe( allItems=> console.log)
Notes:
I guess you could manage to do it using rxjs, but none of the solutions I can think of is simpler than using a set + filter.
I used concatMap to ensure you don't run the request twice. Using mergeMap (flatMap), you could have a scenario like this :
---------Req(user1)----------------------------resp(user1)-------------------------
---Req(user2)--------resp(user2)--Req(user1)-----------------resp(user1)-
But if you accept having eventually more than 2 requests per user, you can use flatMap to gain speed.
I have been trying to use rxjs properly with Redux observable and Redux actions. I've tried many different combinations of calling the below functions and every possible combination has given me different errors.
Basically, I have two actions that can be dispatched in my app: SET_CIRCULAR_DATA and SET_MODAL_OVER_DEEP_LINK_FLAG.
I'd like another action to be dispatched, determineFoundInProducts() when SET_MODAL_OVER_DEEP_LINK_FLAG happens followed by the first (successful) SET_CIRCULAR_DATA action. Any subsequent SET_CIRCULAR_DATA actions should be ignored until SET_MODAL_OVER_DEEP_LINK_FLAG happens again. Because of this need, I felt withLatestFrom() made the most sense. I also thought that takeUntil() and repeat() might be helpful to help me accomplish what I need, though I'm not including them in this code example because I'm stuck at the withLatestFrom() part.
I've tried different combinations of piping, not piping, using switchMap, map, mapTo, but nothing seems to work. I am importing all the used rxjs functions and I'm combining epics at the root, so it couldn't be that. I just can't seem to get the right combination to not get this to error.
Some examples of errors are
Actions must be plain objects. Use custom middleware for async actions.
You provided 'undefined' where a stream was expected
I am using RXJS ^5.5.6 and Redux Observable ^0.17.0.
import 'rxjs';
import { Observable } from 'rxjs/Observable';
import { withLatestFrom, map, tap, mergeMap } from 'rxjs/operators';
import ....all the types and actions
export const handleProductDetailsModalOnLoginRedirect = (action) =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA).pipe(
withLatestFrom(
action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG)
),
mergeMap(([first, second]) => {
console.log(first, second);
return determineFoundInProducts();
})
);
EDIT
I was struggling in my original post to determine what part of the code was breaking. What I had trouble with was that the redux observable action could not communicate with the rxjs functions. What I found was that piping appropriately piped (pun intended) the action to be workable with rxjs functions. Here is what I did.
action.ofType(navigationTypes.types.INIT_REDIRECT_MODAL_LOGIC)
.switchMap(() =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA)
.withLatestFrom(action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG))
.take(1)
.pipe(
map(([first]) => {
// Do stuff
return determineFoundInProducts();
}))
)
The answer depends on what you want to happen if multiple SET_MODAL_OVER_DEEP_LINK_FLAG actions are dispatched before any SET_CIRCULAR_DATA action is, as well as whether they should some how be paired together with some sort of unique ID or not.
It sounds most likely like you're looking for exhaustMap.
The exhaustMap operator is basically the opposite behavior of switchMap. It maps inputs to inner observable and flattens its output, ignore other input values until that observable completes. It exhausts the inner Observable.
In your use case it means that we first listen for SET_MODAL_OVER_DEEP_LINK_FLAG, then we listen for a single SET_CIRCULAR_DATA, but while we're waiting for that subsequent SET_CIRCULAR_DATA we ignore all possibly incoming SET_MODAL_OVER_DEEP_LINK_FLAG actions. Note that the take(1) is important, because otherwise we'll listen for a stream of every SET_CIRCULAR_DATA, not just one.
export const handleProductDetailsModalOnLoginRedirect = (action) =>
action.ofType(navigationTypes.types.SET_MODAL_OVER_DEEP_LINK_FLAG)
.exhaustMap(() =>
action.ofType(weeklyAdTypes.types.SET_CIRCULAR_DATA)
.take(1)
.map(() => determineFoundInProducts())
);
Btw it's common to ask Redux Observable questions that are actually just RxJS questions. This is a cool thing to note because the RxJS community is massive so you'll find a TON more resources on it, as well as have a better chance of getting help if you're able to rephrase questions to be agnostic of Redux Observable--e.g. change ofType('TYPE') to a filter(d => d.type === 'TYPE')
The above answer will definitely help others. However, what I was struggling with was having redux observable action communicate with RXJS functions. Please see my edit to my original post for solution.
Another question about ReactiveUi. I have a ViewModel for an edit form. Model is ReactiveObject. I want to enable savecommand only when changes of object was take place. My try:
var canSaveCommand =
this.WhenAnyValue(vm => vm.CurrentClient)
.Where(client => client != null)
.Select(client =>
client.Changed
)
.Any();
But when the form appears the SaveCommand is already enabled. Where my mistake?
You want to use Switch not SelectMany. SelectMany will not unsubscribe from the previous client. It will merge events from all clients. Switch unsubscribes from the previous client before it subscribes to the next.
var canSaveCommand =
this.WhenAnyValue(vm => vm.CurrentClient)
.Where(client => client != null)
.Select(client =>
client.Changed
)
.Switch()
.Any();
For example the following code makes it clear. Let's say we have a class called AudioChannel It generates audio frames we can can process and send to the speaker.
public class IAudioChannel {
public IObservable<AudioFrame> AudioFrameObservable {get;}
}
Then we might have a list of audio nodes that the user can select but we only want the most current sending audio to the speaker. The below class makes available the currently selected audio node as an observable.
public class AudioListViewModel {
public class IObservable<IAudioChannel> CurrentAudioChannelObservable {get;}
}
Now consider the following code
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.SelectMany(current=>current.AudioFrameObservable)
.Subscribe(frame=>frame.Play());
vs
AudioListViewModel viewModel;
viewModel
.CurrentAudioChannelObservable
.Select(current=>current.AudioFrameObservable)
.Switch()
.Subscribe(frame=>frame.Play());
In the first version as we change the selection of audio nodes we add more and more subscriptions. The audio output quickly becomes a garbled mess of mixed channels. In the second version only one channel is subscribed to at a time and the audio output only plays the output from a single channel.
Many people make this mistake when starting out with RX. For example I found a bug in the ReactiveUI framework that used SelectMany instead of Switch.
However
There is a built in way within ReactiveUI to achieve this in a clear way
There is actually another way to achieve what you want and I will put it in another answer just to show you how to use ReactiveUI.
var canSaveCommand =
this
.WhenAnyObservable(vm => vm.CurrentClient.Changed)
.StartWith(false);
Note that null doesn't have to be explicity handled though you should start with false to make sure a value exists when no observable is available to start with.
WhenAnyObservable
WhenAnyObservable acts a lot like the Rx operator CombineLatest, in
that it watches one or multiple observables and allows you to define a
projection based on the latest value from each. WhenAnyObservable
differs from CombineLatest in that its parameters are expressions,
rather than direct references to the target observables. The impact of
this difference is that the watch set up by WhenAnyObservable is not
tied to the specific observable instances present at the time of
subscription. That is, the observable pointed to by the expression can
be replaced later, and the results of the new observable will still be
captured. An example of where this can come in handy is when a view
wants to observe an observable on a viewmodel, but the viewmodel can
be replaced during the view's lifetime. Rather than needing to
resubscribe to the target observable after every change of viewmodel,
you can use WhenAnyObservable to specify the 'path' to watch. This
allows you to use a single subscription in the view, regardless of the
life of the target viewmodel.
Try changing your Select to a SelectMany. That will then give you an Observable of the changes to be passed into Any instead of an Observable of an Observable of the changes to be passed into Any.
I'm trying to wrap my head around reactive programming and observables.
What is the reactive way to solve the following scenario?
I have an object with 2 properties.
At anytime, one, both or neither of these properties can be set.
Each of these properties dispach events that I can listen to.
Only when both properties are set I want to listen to their update events and run some kind of aggregation on their properties.
One possible solution is to create a stream from each property and then combine the streams using combineLatest.
combineLatest will not produce a value until both inputs have produced a value. After both inputs have produced a value, the stream will update every time either value changes. See code below:
const property1$ = Rx.Observable.fromEvent(button1, 'click');
const property2$ = Rx.Observable.fromEvent(button2, 'click');
const aggregateWhenBothAreClicked$ = property1$
.combineLatest(property2$)
.map([property1,property2])=>doStuff(property1,property2))
The doStuff function will not be called until both buttons have been clicked. Once both buttons have been clicked, the doStuff function will be called everytime afterwards.
I have a node.js-based location service that produces events as users move about, and I want to use RxJS to process these events and look for arbitrarily complex patterns like a user enters a region and visits 2 points of interest within 1 hour.
To begin with I need to split the stream of events base on unique user ids (from event body), but I am not finding any stock RxJS functions that will do this.
filter() would require that all uuids be known beforehand which is not desirable.
groupBy() looks like it would need to process the entire sequence prior to returning the grouped observables, which is not possible.
I'm thinking that maybe I need to build a custom observable that maintains a map of uuids to observables, and instantiate new observables as required. Each of these observables would then need to undergo identical processing in search of the pattern match, and ultimately trigger some action when a user's movements match the pattern. One of the obvious challenges here is I have a dynamically growing map of observables being produced as user enter the system and move about.
Any ideas how something like this could be achieved with RxJS?
I think you are misunderstanding how groupBy() works. It will generate a new Observable every time a new key is generated, and if the key already exists, it will just be pushed to the existing Observable.
So for your problem it should look something like this:
var source = getLocationEvents();
var disposable = new Rx.CompositeDisposable();
disposable.add(
source.groupBy(function(x) { return x.userid; })
.map(function(x) {
return x.map(function(ev) { /*Process the the event*/ });
})
.subscribe(function(group) {
disposable.add(group.subscribe(/*Do something with the event*/));
});