Can you give some non-trivial and not excessively contrived example of when observables should be used? I'm genuinely asking because, while I find them elegant, I still can't figure out why they were put front-and-center of the current generation of web frameworks (especially Angular). They seem a good tool to have but mostly for very particular cases.
I've had a lot of success using observables for side-effects in Redux and NGRX. It's flexible, testable, and reliable.
I can map, filter, etc. my app's actions to perform any side-effect I want, possibly sending a success or failure action on completion. I can even listen for changes in the store or other streams to dispatch actions.
You can still do everything you need with Thunk, Sagas, etc., but I enjoy the ease and flexibility that RxJS gives me.
We are using reactive programming basically everywhere. Example (Angular):
The Angular Router has an Observable "paramMap". We don't subscribe to it but are creating another Observable for the data that should be loaded.
getData$ = (
paramMap$: Observable<ParamMap>
): Observable<MyData> => {
return paramMap$.pipe(
// Create a query string
map(paramMap => {
// some pure function - business logic
return getSomeQueryBasedOnTheRouteParams(
paramMap
);
}),
// get the data
// select$ is a method on the customStore that wraps the http client and does some more stuff
// The switchMap is actually one of the concepts that I found initially not the easiest to understand
switchMap(someQuery=> {
return this.customStore.select$({
query: someQuery
});
}),
map(rawData => {
// pure function with business logic
return modifyData(rawData);
}),
shareReplay()
);
};
Then it goes on with filtering, etc.
getFilteredData$ = (
// filter:$ is in our case a simple Subject we're calling next(newFilterValue)
// NGRX, NGXS or Akita are popular state management libraries to manage this kind of state
filter$: BehaviourSubject<string>,
data$: Observable<MyData>): Observable<MyData> => {
combineLatest(filter$, data$).pipe(
// Do the filtering using pure functions
map( ...
return myFilterFuntion(filter, data);
)
)
};
The filteredData$ Observable can be consumed via async pipe in a component.
The business logic is the same as in imperative-pull code. But using the abstraction of RxJS a lot of boilerplate and indirection vanishes. RxJS is not the easiest abstraction to learn (for me), but as with all, once you're comfortable a lot of the likely intimidating code above will become familiar. You just focus on writing business-logic in pure functions and combine it with RxJS.