I know rxjs has a built in animationFrameScheduler, but i am pretty sure i can not use it to accomplish what I am wanting.
Essentially I am wanting to throttle some events by requestAnimationFrame. How I would do this in a subscribe is:
let taskId;
fromEvent(...)
.subscribe(args => {
if (taskId) {
cancelAnimationFrame(taskId);
}
taskId = requestAnimationFrame(() => {
performMyAction(args);
taskId = null;
});
});
What is happening is I want to throttle the events and only execute the last event per animationFrame.
I have tried throttleTime(0, animationFrameScheduler) and observeOn(animationFrameScheduler) and neither seem to do what I want.
My next thought was to just create a custom scheduler that could do this. I understand that I should create a class that implements ScheduleLike, but after that there seems to be no documentation on what the different methods of that class are supposed to do and what the parameters mean.
Furthermore attempting to read the source code of existing schedulers is an opaque mess of inheritance and wasn't useful is implementing my own.
So my questions is either; how can i use animationFrameScheduler to actually throttle my events in this way, or how can I learn how to build my own scheduler?
Built in animationFrame scheduler combined with audit operator to get last value from the silenced time window should do the job.
See code example:
const { of, from, animationFrameScheduler, asyncScheduler, interval } = rxjs;
const { audit, toArray } = rxjs.operators;
const numbers = Array.from({ length: 100 }).map((_, i) => i);
from(numbers, asyncScheduler).pipe(
audit(e => of(null, animationFrameScheduler))
).subscribe(e => console.log(e));
<script src="https://unpkg.com/rxjs#6.5.3/bundles/rxjs.umd.min.js"></script>
Related
When using Dependency injection in Angular I often need to subscribe to an observable that I haven't yet created!
I often end up using something like this:
// create behavior subject OF Observable<number>
const subject = new BehaviorSubject<Observable<number>>(EMPTY);
// subscribe to it, using flatMap such as to 'unwrap' the observable stream
const unwrappedSubject = subject.pipe(flatMap((x: number) => x));
unwrappedSubject.subscribe(s => console.log(s));
// now actually create the observable stream
const tim = timer(1000, 1000);
// set it into the subject
subject.next(tim);
This uses flatMap to 'unwrap' the observable contained in the subject.
This works fine, but frankly it always feels 'icky'.
What I really want is something like this, where the consumer of the subject treats the instance of the Subject as Observable<number> without having to pipe it every usage.
const subject = new UnwrappingBehaviorSubject<number>(EMPTY);
subject.subscribe((x: number) => console.log(x));
// this could use 'next', but that doesn't feel quite right
subject.setSource(timer(1000, 1000));
I'm aware that I could subscribe to the timer and hook it up directly to the subject, but I also want to avoid an explicit subscribe call because that complicates the responsibility of unsubscribing.
timer(1000, 1000).subscribe(subject);
Is there a nice way to achieve this?
The Subject.ts and BehaviorSubject.ts source files get more complicated than I expected. I'm scared I'll end up with horrible memory leaks if I try to fork it.
I think this would be another way to solve it:
foo.component.ts
export class FooComponent {
private futureObservable$ = new Observable(subscriber => {
// 'Saving' the subscriber for when the observable is ready.
this.futureObservableSubscriber = subscriber;
// The returned function will be invoked when the below mentioned subject instance
// won't have any subscribers(after it had at least one).
return () => this.futureObservableSubscription.unsubscribe();
}).pipe(
// You can mimic the Subject behavior from your initial solution with the
// help of the `share` operator. What it essentially does it to *place*
// a Subject instance here and if multiple subscriptions occur, this Subject instance
// will keep track of all of them.
// Also, when the first subscriber is registered, the observable source(the Observable constructor's callback)
// will be invoked.
share()
);
private futureObservableSubscriber = null;
// We're using a subscription so that it's easier to collect subscriptions to this observable.
// It's also easier to unsubscribe from all of them at once.
private futureObservableSubscription = new Subscription();
constructor (/* ... */) {};
ngOnInit () {
// If you're using `share`, you're safe to have multiple subscribers.
// Otherwise, the Observable's callback(i.e `subscriber => {...}`) will be called multiple times.
futureObservable$.subscribe(/* ... */);
futureObservable$.subscribe(/* ... */);
}
whenObservableReady () {
const tim = timer(1000, 1000);
// Here we're adding the subscription so that is unsubscribed when the main observable
// is unsubscribed. This part can be found in the returned function from the Observable's callback.
this.futureObservableSubscription.add(tim.subscribe(this.futureObservableSubscriber));
}
};
Indeed, a possible downside is that you'll have to explicitly subscribe, e.g in the whenObservableReady method.
With this approach you can also have different sources:
whenAnotherObservableReady () {
// If you omit this, it should mean that you will have multiple sources at the same time.
this.cleanUpCrtSubscription();
const tim2 = timer(5000, 5000);
this.futureObservableSubscription.add(tim2.subscribe(this.futureObservableSubscriber));
}
private cleanUpCrtSubscription () {
// Removing the subscription created from the current observable(`tim`).
this.futureObservableSubscription.unsubscribe();
this.futureObservableSubscription = new Subscription();
}
As part of learning rxjs ive been using create methods of, from, interval etc. to test throttle and deboucne etc ive been creating streams using fromevent.
now i have a real use case and i need to dynamically add values into an empty observable stream. i cant find any examples on how best to do this NOT using the creation methods above. Presently Im using a BehaviourSubject to dynamically add items to a stream using next(). Is this the best/preferred way of DYNAMICALLY adding new items to a stream?
e.g.
import { BehaviorSubject, timer } from 'rxjs';
import { tap, mapTo, concatMap, } from 'rxjs/operators';
const subject = new BehaviorSubject(1);
const example = subject.pipe(
concatMap(ev => timer(200).pipe(mapTo(ev))),
tap((ev) => console.log(ev))
)
example.subscribe();
// add a flurry of values dynamically
subject.next(2);
subject.next(3);
subject.next(4);
// some time later add some more
setTimeout(function(){
subject.next(5);
subject.next(6);
subject.next(7);
}, 5000);
https://stackblitz.com/edit/rxjs-behaviorsubject-simpleexample-gyrtw8?file=index.ts
Thanks
If you have a veeery custom logic of adding values that should be emitted in an Observable, you can create your own (instead of using fromEvent, of, from, ...):
const myObservable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.next(5);
setTimeout(() => {
subscriber.next(6);
}, 2000);
}, 1000);
});
However, the rxjs's creation functions should cover 99% of your needs.
The code above can be also written like:
concat(
of(1,2,3),
of(4,5).pipe(
delay(1000)
),
of(6).pipe(
delay(2000)
)
)
UPD: About Subjects
Subject is also an Observable so in your case using Subject is applicable but might not be the best option. The idea of a Subject is that there can be more than one subscriber (the one who uses the values from subject) but I'm not sure that it's your case (by the way - you can provide your real-life example to help us understand what you want to achieve)
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 SPA that is loading some global/shared data (let's call this APP_LOAD_OK) and page-specific data (DASHBOARD_LOAD_OK) from the server. I want to show a loading animation until both APP_LOAD_OK and DASHBOARD_LOAD_OK are dispatched.
Now I have a problem with expressing this in RxJS. What I need is to trigger an action after each DASHBOARD_LOAD_OK, as long as there had been at least one APP_LOAD_OK. Something like this:
action$
.ofType(DASHBOARD_LOAD_OK)
.waitUntil(action$.ofType(APP_LOAD_OK).first())
.mapTo(...)
Does anybody know, how I can express it in valid RxJS?
You can use withLatestFrom since it will wait until both sources emit at least once before emitting. If you use the DASHBOARD_LOAD_OK as the primary source:
action$.ofType(DASHBOARD_LOAD_OK)
.withLatestFrom(action$.ofType(APP_LOAD_OK) /*Optionally*/.take(1))
.mapTo(/*...*/);
This allows you to keep emitting in the case that DASHBOARD_LOAD_OK fires more than once.
I wanted to avoid implementing a new operator, because I thought my RxJS knowledge was not good enough for that, but it turned out to be easier than I thought. I am keeping this open in case somebody has a nicer solution. Below you can find the code.
Observable.prototype.waitUntil = function(trigger) {
const source = this;
let buffer = [];
let completed = false;
return Observable.create(observer => {
trigger.subscribe(
undefined,
undefined,
() => {
buffer.forEach(data => observer.next(data));
buffer = undefined;
completed = true;
});
source.subscribe(
data => {
if (completed) {
observer.next(data);
} else {
buffer.push(data);
}
},
observer.error.bind(observer),
observer.complete.bind(observer)
);
});
};
If you want to receive every DASHBOARD_LOAD_OK after the first APP_LOAD_OK You can simply use skipUntil:
action$ .ofType(DASHBOARD_LOAD_OK)
.skipUntil(action$.ofType(APP_LOAD_OK).Take(1))
.mapTo(...)
This would only start emitting DASHBOARD_LOAD_OK actions after the first APP_LOAD_OK, all actions before are ignored.
I'm new to ReactiveX/RxJs and I'm wondering if my use-case is feasible smoothly with RxJs, preferably with a combination of built-in operators. Here's what I want to achieve:
I have an Angular2 application that communicates with a REST API. Different parts of the application need to access the same information at different times. To avoid hammering the servers by firing the same request over and over, I'd like to add client-side caching. The caching should happen in a service layer, where the network calls are actually made. This service layer then just hands out Observables. The caching must be transparent to the rest of the application: it should only be aware of Observables, not the caching.
So initially, a particular piece of information from the REST API should be retrieved only once per, let's say, 60 seconds, even if there's a dozen components requesting this information from the service within those 60 seconds. Each subscriber must be given the (single) last value from the Observable upon subscription.
Currently, I managed to achieve exactly that with an approach like this:
public getInformation(): Observable<Information> {
if (!this.information) {
this.information = this.restService.get('/information/')
.cache(1, 60000);
}
return this.information;
}
In this example, restService.get(...) performs the actual network call and returns an Observable, much like Angular's http Service.
The problem with this approach is refreshing the cache: While it makes sure the network call is executed exactly once, and that the cached value will no longer be pushed to new subscribers after 60 seconds, it doesn't re-execute the initial request after the cache expires. So subscriptions that occur after the 60sec cache will not be given any value from the Observable.
Would it be possible to re-execute the initial request if a new subscription happens after the cache timed out, and to re-cache the new value for 60sec again?
As a bonus: it would be even cooler if existing subscriptions (e.g. those who initiated the first network call) would get the refreshed value whose fetching had been initiated by the newer subscription, so that once the information is refreshed, it is immediately passed through the whole Observable-aware application.
I figured out a solution to achieve exactly what I was looking for. It might go against ReactiveX nomenclature and best practices, but technically, it does exactly what I want it to. That being said, if someone still finds a way to achieve the same with just built-in operators, I'll be happy to accept a better answer.
So basically since I need a way to re-trigger the network call upon subscription (no polling, no timer), I looked at how the ReplaySubject is implemented and even used it as my base class. I then created a callback-based class RefreshingReplaySubject (naming improvements welcome!). Here it is:
export class RefreshingReplaySubject<T> extends ReplaySubject<T> {
private providerCallback: () => Observable<T>;
private lastProviderTrigger: number;
private windowTime;
constructor(providerCallback: () => Observable<T>, windowTime?: number) {
// Cache exactly 1 item forever in the ReplaySubject
super(1);
this.windowTime = windowTime || 60000;
this.lastProviderTrigger = 0;
this.providerCallback = providerCallback;
}
protected _subscribe(subscriber: Subscriber<T>): Subscription {
// Hook into the subscribe method to trigger refreshing
this._triggerProviderIfRequired();
return super._subscribe(subscriber);
}
protected _triggerProviderIfRequired() {
let now = this._getNow();
if ((now - this.lastProviderTrigger) > this.windowTime) {
// Data considered stale, provider triggering required...
this.lastProviderTrigger = now;
this.providerCallback().first().subscribe((t: T) => this.next(t));
}
}
}
And here is the resulting usage:
public getInformation(): Observable<Information> {
if (!this.information) {
this.information = new RefreshingReplaySubject(
() => this.restService.get('/information/'),
60000
);
}
return this.information;
}
To implement this, you will need to create your own observable with custom logic on subscribtion:
function createTimedCache(doRequest, expireTime) {
let lastCallTime = 0;
let lastResult = null;
const result$ = new Rx.Subject();
return Rx.Observable.create(observer => {
const time = Date.now();
if (time - lastCallTime < expireTime) {
return (lastResult
// when result already received
? result$.startWith(lastResult)
// still waiting for result
: result$
).subscribe(observer);
}
const disposable = result$.subscribe(observer);
lastCallTime = time;
lastResult = null;
doRequest()
.do(result => {
lastResult = result;
})
.subscribe(v => result$.next(v), e => result$.error(e));
return disposable;
});
}
and resulting usage would be following:
this.information = createTimedCache(
() => this.restService.get('/information/'),
60000
);
usage example: https://jsbin.com/hutikesoqa/edit?js,console