In many articles I find that tap operator is a good way to perform side effects. My question is about a difference between performing side effects using subscribe and tap. Here are examples which are doing actually the same:
this.store$
.pipe(tap(x => {
this.store = x;
}));
this.store$
.subscribe(x => {
this.store = x;
});
Is there any difference in the performance or is there any reason to use one of these approaches?
In Angular context.
You may have a component say MyComponent. There is a service as well for this component MyService. This service is responsible for some complex operation in MyComponent. Now you will make the subscription in the MyComponent as
$myObs.subscribe((val) => //do something );
But same data may be required in your service as well, so you define it in service as
$myObs.pipe(tap((val) => // do something with data in service));
Subscription will be done in component but using tap you can assign data member variable in your service as well.
Related
In our Single Page Application we've developed a centralized store class that uses an RxJS behavior subject to handle the state of our application and all its mutation. Several components in our application are subscribing to our store's behavior subject in order to receive any update to current application state. This state is then bound to UI so that whenever state changes, UI reflect those changes. Whenever a component wants to change a part of the state, we call a function exposed by our store that does the required work and updates the state calling next on the behavior subject. So far nothing special. (We're using Aurelia as a framework which performs 2 way binding)
The issue we are facing is that as soon as a component changes it's local state variable it receives from the store, other components gets updated even if next() wasn't called on the subejct itself.
We also tried to subscribe on an observable version of the subject since observable are supposed to send a different copy of the data to all subscriber but looks like it's not the case.
Looks like all subject subscriber are receiving a reference of the object stored in the behavior subject.
import { BehaviorSubject, of } from 'rxjs';
const initialState = {
data: {
id: 1,
description: 'initial'
}
}
const subject = new BehaviorSubject(initialState);
const observable = subject.asObservable();
let stateFromSubject; //Result after subscription to subject
let stateFromObservable; //Result after subscription to observable
subject.subscribe((val) => {
console.log(`**Received ${val.data.id} from subject`);
stateFromSubject = val;
});
observable.subscribe((val) => {
console.log(`**Received ${val.data.id} from observable`);
stateFromObservable = val;
});
stateFromSubject.data.id = 2;
// Both stateFromObservable and subject.getValue() now have a id of 2.
// next() wasn't called on the subject but its state got changed anyway
stateFromObservable.data.id = 3;
// Since observable aren't bi-directional I thought this would be a possible solution but same applies and all variable now shows 3
I've made a stackblitz with the code above.
https://stackblitz.com/edit/rxjs-bhkd5n
The only workaround we have so far is to clone the sate in some of our subscriber where we support edition through binding like follow:
observable.subscribe((val) => {
stateFromObservable = JSON.parse(JSON.stringify(val));
});
But this feels more like a hack than a real solution. There must be a better way...
Yes, all subscribers receive the same instance of the object in the behavior subject, that is how behavior subjects work. If you are going to mutate the objects you need to clone them.
I use this function to clone my objects I am going to bind to Angular forms
const clone = obj =>
Array.isArray(obj)
? obj.map(item => clone(item))
: obj instanceof Date
? new Date(obj.getTime())
: obj && typeof obj === 'object'
? Object.getOwnPropertyNames(obj).reduce((o, prop) => {
o[prop] = clone(obj[prop]);
return o;
}, {})
: obj;
So if you have an observable data$ you can create an observable clone$ where subscribers to that observable get a clone that can be mutated without affecting other components.
clone$ = data$.pipe(map(data => clone(data)));
So components that are just displaying data can subscribe to data$ for efficiency and ones that will mutate the data can subscribe to clone$.
Have a read on my library for Angular https://github.com/adriandavidbrand/ngx-rxcache and my article on it https://medium.com/#adrianbrand/angular-state-management-with-rxcache-468a865fc3fb it goes into the need to clone objects so we don't mutate data we bind to forms.
It sounds like the goals of your store are the same as my Angular state management library. It might give you some ideas.
I am not familar with Aurelia or if it has pipes but that clone function is available in the store with exposing my data with a clone$ observable and in the templates with a clone pipe that can be used like
data$ | clone as data
The important part is knowing when to clone and not to clone. You only need to clone if the data is going to be mutated. It would be really inefficient to clone an array of data that is only going to be displayed in a grid.
The only workaround we have so far is to clone the state in some of our subscriber where we support edition through binding like follow:
I don't think I can answer that without rewriting your store.
const initialState = {
data: {
id: 1,
description: 'initial'
}
}
That state object has deeply structured data. Everytime you need to mutate the state the object needs to be reconstructed.
Alternatively,
const initialState = {
1: {id: 1, description: 'initial'},
2: {id: 2, description: 'initial'},
3: {id: 3, description: 'initial'},
_index: [1, 2, 3]
};
That is about as deep of a state object that I would create. Use a key/value pair to map between IDs and the object values. You can now write selectors easily.
function getById(id: number): Observable<any> {
return subject.pipe(
map(state => state[id]),
distinctUntilChanged()
);
}
function getIds(): Observable<number[]> {
return subject.pipe(
map(state => state._index),
distinctUntilChanged()
);
}
When you want change a data object. You have to reconstruct the state and also set the data.
function append(data: Object) {
const state = subject.value;
subject.next({...state, [data.id]: Object.freeze(data), _index: [...state._index, data.id]});
}
function remove(id: number) {
const state = {...subject.value};
delete state[id];
subject.next({...state, _index: state._index.filter(x => x !== id)});
}
Once you have that done. You should freeze downstream consumers of your state object.
const subject = new BehaviorSubject(initialState);
function getStore(): Observable<any> {
return subject.pipe(
map(obj => Object.freeze(obj))
);
}
function getById(id: number): Observable<any> {
return getStore().pipe(
map(state => state[id]),
distinctUntilChanged()
);
}
function getIds(): Observable<number[]> {
return getStore().pipe(
map(state => state._index),
distinctUntilChanged()
);
}
Later when you do something like this:
stateFromSubject.data.id = 2;
You'll get a run-time error.
FYI: The above is written in TypeScript
The big logical issue with your example is that the object forwarded by the subject is actually a single object reference. RxJS doesn't do anything out of the box to create clones for you, and that is fine otherwise it would result in unnecessary operations by default if they aren't needed.
So while you can clone the value received by the subscribers, you're still not save for access of BehaviorSubject.getValue(), which would return the original reference. Besides that having same refs for parts of your state is actually beneficial in lots of ways as e.g arrays can be re-used for multiple displaying components vs having to rebuild them from scratch.
What you want to do instead is to leverage a single-source-of-truth pattern, similar to Redux, where instead of making sure that subscribers get clones, you're treating your state as immutable object. That means every modification results in a new state. That further means you should restrict modifications to actions, (actions + reducers in Redux) which construct a new state of the current plus the necessary changes and return the new copy.
Now all of that might sound like a lot of work but you should take a look at the official Aurelia Store Plugin, which is sharing pretty much the same concept as you have plus making sure that best ideas of Redux are brought over to the world of Aurelia.
Because BehaviorSubject extends Subject and Subject extends Observable, all of those three have static .create(observer) method to create them using custom values emission logic.
I' able to use with good result Observable.create(observer), for instance:
a = Rx.Observable.create(obs => {
setInterval(() => {
obs.next('tick');
}, 500)
})
s = a.subscribe(v => console.log(v))
Gives me expected output (tick every 500ms)
But when I replace Observable with Subject/BehaviorSubject, it's not so willing to get up and running:
a = Rx.Subject.create(obs => {
setInterval(() => {
obs.next('tick');
}, 500)
})
s = a.subscribe(v => console.log(v)); // Nothing
a.next(5); // Still nothing
Basically, subject seems to work as intended to only if they are created via new operator like below:
a = new Rx.Subject();
s = a.subscribe(v => {console.log(v)});
a.next(5) // Ok, got value here
Even if I try to use non-parametrized create method, which invocation shall boil down to same result as using new:
a = Rx.Subject.create();
I'm still unable to force it to emit values.
I'm aware that subjects are designed to receive values from outside world (not to generate them internally as Observables), thus subject shall be triggered by external code with subject.next('value'), but I was just curios that if they are strictly related to Observables, logic behind create and further behavior shall be same...
Can anyone explain, why usage of create on Subject (even if they are not designed to work this way, but still it shall be possible) does not work as supposed to?
I have an Angular 2 service:
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
}
}
Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?
A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.
If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.
It also has a method getValue() to get the current value.
The only way you should be getting values "out of" an Observable/Subject is with subscribe!
If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.
There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
I had similar situation where late subscribers subscribe to the Subject after its value arrived.
I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case.
And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject
const observable = of('response')
function hasValue(value: any) {
return value !== null && value !== undefined;
}
function getValue<T>(observable: Observable<T>): Promise<T> {
return observable
.pipe(
filter(hasValue),
first()
)
.toPromise();
}
const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................
You can check the full article on how to implement it from here.
https://www.imkrish.com/blog/development/simple-way-get-value-from-observable
I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
isLoggedIn: boolean;
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
this.currIsLoggedIn = false;
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
this.isLoggedIn = value;
}
}
A component that needs the current value could just then access it from the service, i.e,:
sessionStorage.isLoggedIn
Not sure if this is the right practice :)
A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.
While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.
If this is the case then when you subscribe, the value will come back immediately.
So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:
You might try to do this (and you should as much as possible keep things 'inside pipes'):
serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
.subscribe(([ serviceCallResponse, customer] => {
// we have serviceCallResponse and customer
});
The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.
I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:
serviceCallResponse$.pipe()
.subscribe(serviceCallResponse => {
// immediately try to subscribe to get the 'available' value
// note: immediately unsubscribe afterward to 'cancel' if needed
let customer = undefined;
// whatever the secondary observable is
const secondary$ = store$.select(x => x.customer);
// subscribe to it, and assign to closure scope
sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
sub.unsubscribe();
// if there's a delay or customer isn't available the value won't have been set before we get here
if (customer === undefined)
{
// handle, or ignore as needed
return throwError('Customer was not immediately available');
}
});
Note that for all of the above I'm using subscribe to get the value (as #Ben discusses). Not using a .value property, even if I had a BehaviorSubject.
Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...
You could always create an extension getter to get the current value of an Observable.
To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.
You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.
// global.d.ts
declare module 'rxjs' {
interface Observable<T> {
/**
* _Extension Method_ - Returns current value of an Observable.
* Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
*/
value: Observable<T>;
}
}
// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
get <T>(this: Observable<T>): Observable<T> {
return this.pipe(
filter(value => value !== null && value !== undefined),
first());
},
});
// using the extension getter example
this.myObservable$.value
.subscribe(value => {
// whatever code you need...
});
There are two ways you can achieve this.
BehaviorSubject has a method getValue() which you can get the value in a specific point of time.
You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.
I wouldn't recommend both approaches.
In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.
The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.
I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
With this, you may use it directly to the UI by piping data$ | async in case of angular.
A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.
Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"
ngOnInit() {
...
// If loading with previously saved value
if (this.controlValue) {
// Take says once you have 1, then close the subscription
this.selectList.pipe(take(1)).subscribe(x => {
let opt = x.find(y => y.value === this.controlValue);
this.updateValue(opt);
});
}
}
You could store the last emitted value separately from the Observable. Then read it when needed.
let lastValue: number;
const subscription = new Service().start();
subscription
.subscribe((data) => {
lastValue = data;
}
);
The best way to do this is using Behaviur Subject, here is an example:
var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:
async myFunction () {
const currentValue = await firstValueFrom(
of(0).pipe(
withLatestFrom(this.yourObservable$),
map((tuple) => tuple[1]),
take(1)
)
);
// do stuff with current value
}
This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.
I have a class that when instantiated makes some web service calls, pseudo code below:
Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'))
.flatMap(function () {
return Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'));
});
The same class is listening for an onclick event.
When this even is triggered, if the original web service calls are complete: do something
If they are not complete, wait for them to complete, before doing something.
I was wondering how to achieve this with the rxjs approach? rather than setting variables and using if statements.
I would refer to this as an Asynchronous Gate.
These are actually pretty easy to do with Rx.
You will need to cache the web service calls observable sequences.
Then in other calls that are predicated on these being complete, you simply flatMap off their results.
As these are from Promises I believe the result is retained for late subscribers, but if not then you just need to replay(1) the sequences.
So in psudeo code
var startUpData = Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'))
.flatMap(function () {
return Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'));
});
var events = Rx.Observable....//Your event wired up here.
//When an event
events
.flatMap(function(evt){
//Wait until the startUpData yeilds, but pass on the evt data instead.
return startUpData.map(function(){ return evt;})
//do something here knowing that your event has fired, but the web services have also completed.
.subscribe();
You can see Matt Barrett explain an Async gate in this video at about 51minutes in to this video - https://youtu.be/Tp5mRlHwZ7M?t=51m30s
You may also want to consider the switch operator incase you don't want overlapping events
I believe withLatestFrom or combineLatest will do what you're asking.
Depending on if you wish to only allow the button to be clicked once with the data provided from the service you could use withLatestFrom. If you wish to allow the button to continued to be clicked using the data previously provided by the service you can use combineLatest
const futureEvent$ = Rx.Observable.timer(3000);
const btnClick$ = Rx.Observable
.fromEvent(document.querySelector('button'), 'click');
const futureAndBtnClick$ = futureEvent$.combineLatest(btnClick$);
futureAndBtnClick$.subscribe(x => console.log('click + future stuff happened'));
jsbin example
I am wondering what is the use of asObservable:
As per docs:
An observable sequence that hides the identity of the
source sequence.
But why would you need to hide the sequence?
When to use Subject.prototype.asObservable()
The purpose of this is to prevent leaking the "observer side" of the Subject out of an API. Basically to prevent a leaky abstraction when you don't want people to be able to "next" into the resulting observable.
Example
(NOTE: This really isn't how you should make a data source like this into an Observable, instead you should use the new Observable constructor, See below).
const myAPI = {
getData: () => {
const subject = new Subject();
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subject.next({ type: 'message', data });
source.onOtherMessage = (data) => subject.next({ type: 'othermessage', data });
return subject.asObservable();
}
};
Now when someone gets the observable result from myAPI.getData() they can't next values in to the result:
const result = myAPI.getData();
result.next('LOL hax!'); // throws an error because `next` doesn't exist
You should usually be using new Observable(), though
In the example above, we're probably creating something we didn't mean to. For one, getData() isn't lazy like most observables, it's going to create the underlying data source SomeWeirdDataSource (and presumably some side effects) immediately. This also means if you retry or repeat the resulting observable, it's not going to work like you think it will.
It's better to encapsulate the creation of your data source within your observable like so:
const myAPI = {
getData: () => return new Observable(subscriber => {
const source = new SomeWeirdDataSource();
source.onMessage = (data) => subscriber.next({ type: 'message', data });
source.onOtherMessage = (data) => subscriber.next({ type: 'othermessage', data });
return () => {
// Even better, now we can tear down the data source for cancellation!
source.destroy();
};
});
}
With the code above, any behavior, including making it "not lazy" can be composed on top of the observable using RxJS's existing operators.
A Subject can act both as an observer and an observable.
An Obervable has 2 methods.
subscribe
unsubscribe
Whenever you subscribe to an observable, you get an observer which has next, error and complete methods on it.
You'd need to hide the sequence because you don't want the stream source to be publicly available in every component. You can refer to #BenLesh's example, for the same.
P.S. : When I first-time came through Reactive Javascript, I was not able to understand asObservable. Because I had to make sure I understand the basics clearly and then go for asObservable. :)
In addition to this answer I would mention that in my opinion it depends on the language in use.
For untyped (or weakly typed) languages like JavaScript it might make sense to conceal the source object from the caller by creating a delegate object like asObservable() method does. Although if you think about it it won't prevent a caller from doing observable.source.next(...). So this technique doesn't prevent the Subject API from leaking, but it indeed makes it more hidden form the caller.
On the other hand, for strongly typed languages like TypeScript the method asObservable() doesn't seem to make much sense (if any).
Statically typed languages solve the API leakage problem by simply utilizing the type system (e.g. interfaces). For example, if your getData() method is defined as returning Observable<T> then you can safely return the original Subject, and the caller will get a compilation error if attempting to call getData().next() on it.
Think about this modified example:
let myAPI: { getData: () => Observable<any> }
myAPI = {
getData: () => {
const subject = new Subject()
// ... stuff ...
return subject
}
}
myAPI.getData().next() // <--- error TS2339: Property 'next' does not exist on type 'Observable<any>'
Of course, since it all compiles to JavaScript in the end of the day there might still be cases when you want to create a delegate. But my point is that the room for those cases is much smaller then when using vanilla JavaScript , and probably in majority of cases you don't need that method.
(Typescript Only) Use Types Instead of asObservable()
I like what Alex Vayda is saying about using types instead, so I'm going to add some additional information to clarify.
If you use asObservable(), then you are running the following code.
/**
* Creates a new Observable with this Subject as the source. You can do this
* to create customize Observer-side logic of the Subject and conceal it from
* code that uses the Observable.
* #return {Observable} Observable that the Subject casts to
*/
asObservable(): Observable<T> {
const observable = new Observable<T>();
(<any>observable).source = this;
return observable;
}
This is useful for Javascript, but not needed in Typescript. I'll explain why below.
Example
export class ExampleViewModel {
// I don't want the outside codeworld to be able to set this INPUT
// so I'm going to make it private. This means it's scoped to this class
// and only this class can set it.
private _exampleData = new BehaviorSubject<ExampleData>(null);
// I do however want the outside codeworld to be able to listen to
// this data source as an OUTPUT. Therefore, I make it public so that
// any code that has reference to this class can listen to this data
// source, but can't write to it because of a type cast.
// So I write this
public exampleData$ = this._exampleData as Observable<ExampleData>;
// and not this
// public exampleData$ = this._exampleData.asObservable();
}
Both do the samething, but one doesn't add additional code calls or memory allocation to your program.
❌this._exampleData.asObservable();❌
Requires additional memory allocation and computation at runtime.
✅this._exampleData as Observable<ExampleData>;✅
Handled by the type system and will NOT add additional code or memory allocation at runtime.
Conclusion
If your colleagues try this, referenceToExampleViewModel.exampleData$.next(new ExampleData());, then it will be caught at compile time and the type system won't let them, because exampleData$ has been casted to Observable<ExampleData> and is no longer of type BehaviorSubject<ExampleData>, but they will be able to listen (.subscribe()) to that source of data or extend it (.pipe()).
This is useful when you only want a particular service or class to be setting the source of information. It also helps with separating the input from the output, which makes debugging easier.