So I'm relatively inexperienced with rxjs so if this is something that would be a pain or really awkward to do, please tell me and I'll go a different route. So in this particular use case, I was to queue up updates to send to the server, but if there's an update "in flight" I want to only keep the latest item which will be sent when the current in flight request completes.
I am kind of at a loss of where to start honestly. It seems like this would be either a buffer type operator and/or a concat map.
Here's what I would expect to happen:
const updateQueue$ = new Subject<ISettings>()
function sendToServer (settings: ISettings): Observable {...}
...
// we should send this immediately because there's nothing in-flight
updateQueue$.next({ volume: 25 });
updateQueue$.next({ volume: 30 });
updateQueue$.next({ volume: 50 });
updateQueue$.next({ volume: 65 });
// lets assume that our our original update just completed
// I would now expect a new request to go out with `{ volume: 65 }` and the previous two to be ignored.
I think you can achieve what you want with this:
const allowNext$ = new Subject<boolean>()
const updateQueue$ = new Subject<ISettings>()
function sendToServer (settings: ISettings): Observable { ... }
updateQueue$
.pipe(
// Pass along flag to mark the first emitted value
map((value, index) => {
const isFirstValue = index === 0
return { value, isFirstValue }
}),
// Allow the first value through immediately
// Debounce the rest until subject emits
debounce(({ isFirstValue }) => isFirstValue ? of(true) : allowNext$),
// Send network request
switchMap(({ value }) => sendToServer(value)),
// Push to subject to allow next debounced value through
tap(() => allowNext$.next(true))
)
.subscribe(response => {
...
})
This is a pretty interesting question.
If you did not have the requirement of issuing the last in the queue, but simply ignoring all requests of update until the one on the fly completes, than you would simply have to use exhaustMap operator.
But the fact that you want to ignore all BUT the last request for update makes the potential solution a bit more complex.
If I understand the problem well, I would proceed as follows.
First of all I would define 2 Subjects, one that emits the values for the update operation (i.e. the one you have already defined) and one dedicated to emit only the last one in the queue if there is one.
The code would look like this
let lastUpdate: ISettings;
const _updateQueue$ = new Subject<ISettings>();
const updateQueue$ = _updateQueue$
.asObservable()
.pipe(tap(settings => (lastUpdate = settings)));
const _lastUpdate$ = new Subject<ISettings>();
const lastUpdate$ = _lastUpdate$.asObservable().pipe(
tap(() => (lastUpdate = null)),
delay(0)
);
Then I would merge the 2 Observables to obtain the stream you are looking for, like this
merge(updateQueue$, lastUpdate$)
.pipe(
exhaustMap(settings => sendToServer(settings))
)
.subscribe({
next: res => {
// do something with the response
if (lastUpdate) {
// emit only if there is a new "last one" in the queue
_lastUpdate$.next(lastUpdate);
}
},
});
You may notice that the variable lastUpdate is used to control that the last update in the queue is used only once.
In my application, I came across a strange behavior related to combineLatest operator. I reproduced this issue with the online demo:
Note: Please ignore this business logic in this demo, it's not that reasonable, I just want to reproduce this issue in technical level.
https://stackblitz.com/edit/angular-qcdslo?file=src/app/app.component.ts
private testRequest() {
this.pokemon$ = combineLatest(this.limit$, this.offset$)
.pipe(
map(data => ({limit: data[0], offset: data[1]})),
switchMap(data => this.pokemonService.getPokemon(data.limit, data.offset)),
map((response: {results: Pokemon[]}) => response.results),
);
}
this method use combineLatest to combine two observables: limit$ and offset$. And sent request to the API where the value of limit and offset are just parameters for the API.
And I increase the counter value by 1 per 5 seconds in the following methods:
let counter = 1
setInterval(() => {
this.offsetControl.setValue(counter)
counter++;
}, 5000)
Finally, for some reason I need to call the testRequest method intervally per 6s as well in the following way:
setInterval(() => {
this.testRequest();
}, 6000)
Then the network request behavior is as below:
limit=5&offset=0
limit=5&offset=1
limit=5&offset=0
limit=5&offset=2
limit=5&offset=0
limit=5&offset=3
...
limit=5&offset=0
limit=5&offset=n
I don't understand why the limit=5&offset=0 happened repeatedly. Thank you.
Everytime testRequest executes you are creating a new observable, this is not what you want to do, everytime you recreate the observable you are getting the startsWith value that was zero when you called startsWith.
Get rid of the combine latest and just pull the values from the form controls.
https://stackblitz.com/edit/angular-droqf6?file=src/app/app.component.ts
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.
Let's say I have a rather typical use of rx that does requests every time some change event comes in (I write this in the .NET style, but I'm really thinking of Javascript):
myChanges
.Throttle(200)
.Select(async data => {
await someLongRunningWriteRequest(data);
})
If the request takes longer than 200ms, there's a chance a new request begins before the old one is done - potentially even that the new request is completed first.
How to synchronize this?
Note that this has nothing to do with multithreading, and that's the only thing I could find information about when googling for "rx synchronization" or something similar.
You could use concatMap operator which will start working on the next item only after previous was completed.
Here is an example where events$ appear with the interval of 200ms and then processed successively with a different duration:
const { Observable } = Rx;
const fakeWriteRequest = data => {
console.log('started working on: ', data);
return Observable.of(data).delay(Math.random() * 2000);
}
const events$ = Observable.interval(200);
events$.take(10)
.concatMap(i => fakeWriteRequest(i))
.subscribe(e => console.log(e));
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
I'm writing an Angular app which uses the ReactiveX API to handle asynchronous operations. I used the API before in an Android project and I really like how it simplifies concurrent task handling. But there is one thing which I'm not sure how to solve in a right way.
How to update observer from an ongoing task? The task in this case will take time to load/create a complex/large object and I'm able to return intermediate progress, but not the object itself. The observable can only return one dataType. Therefor I know two possibilities.
Create an object which has a progress field and a data field. This object can be simply returned with Observable.onNext(object). The progress field will update on every onNext, while the data field is empty until the last onNext, which will set it to the loaded value.
Create two observables, a data observable and a progress observable. The observer hast to subscribe to the progress observable for progress updates and to the data observable to be notified when the data is finally loaded/created. These can also be optionally be zipped together for one subscription.
I used both techniques, they both work, but I want to know if there is a unified standard, a clean way, how to solve this task. It can, of course, as well be a completly new one. Im open for every solution.
After careful consideration I use a
solution similar to option two in my question.
The main observable is concerned with the actual result of
the operation.
A http request in this case, but the File iteration example is similar.
It is returned by the "work" function.
A second Observer/Subscriber can be added through a function parameter. This subscriber is concerned only with
the progress information. This way all operations are nullsafe and no type checks are needed.
A second version of the work function, without the progress Observer,
can be used if no progress UI update is needed.
export class FileUploadService {
doWork(formData: FormData, url: string): Subject<Response> {
return this.privateDoWork(formData, url, null);
}
doWorkWithProgress(formData: FormData, url: string, progressObserver: Observer<number>): Subject<Response> {
return this.privateDoWork(formData, url, progressObserver);
}
private privateDoWork(formData: FormData, url: string, progressObserver: Observer<number> | null): Subject<Response> {
return Observable.create(resultObserver => {
let xhr: XMLHttpRequest = new XMLHttpRequest();
xhr.open("POST", url);
xhr.onload = (evt) => {
if (progressObserver) {
progressObserver.next(1);
progressObserver.complete();
}
resultObserver.next((<any>evt.target).response);
resultObserver.complete()
};
xhr.upload.onprogress = (evt) => {
if (progressObserver) {
progressObserver.next(evt.loaded / evt.total);
}
};
xhr.onabort = (evt) => resultObserver.error("Upload aborted by user");
xhr.onerror = (evt) => resultObserver.error("Error");
xhr.send(formData);
});
}
Here is a call of the function including the progress Subscriber. With this solution the caller of the upload function must
create/handle/teardown the progress subscriber.
this.fileUploadService.doWorkWithProgress(this.chosenSerie.formData, url, new Subscriber((progress) => console.log(progress * 100)).subscribe(
(result) => console.log(result),
(error) => console.log(error),
() => console.log("request Completed")
);
Overall I prefered this solution to a "Pair" Object with a single subscription. There is no null handling nececcary, and
I got a clean seperation of concerns.
The example is written in Typescript, but similar solutions should be possible with other ReactiveX implementations.