How is it possible to stop a debounced Rxjs Observable? - javascript

I created an observable, which will fire 3 seconds after the last change is made, and calls the publishChange of the service. It works, but I would like to create a doImmediateChange function, which calls publishChange immediately and stops the debounced observable. How is that possible?
My component:
class MyComponent {
private updateSubject = new Subject<string>();
ngOnInit() {
this.updateSubject.pipe(
debounceTime(3000),
distinctUntilChanged()
).subscribe(val => {
this.srv.publishChange(val);
});
}
doChange(val: string) {
this.updateSubject.next(val);
}
doImmediateChange(val: string) {
// Stop the current updateSubject if debounce is in progress and call publish immediately
// ??
this.srv.publishChange(val);
}
}

You can emulate debounceTime using switchMap and delay. Then cancel the inner Observable with takeUntil to prevent a waiting value from being emitted.
private updateSubject = new Subject<string>();
private interrupt = new Subject();
ngOnInit() {
this.updateSubject.pipe(
switchMap(val => of(val).pipe(
delay(3000),
takeUntil(this.interrupt)
))
).subscribe(val => publish(val));
}
doChange(val: string) {
this.updateSubject.next(val);
}
doImmediateChange(val: string) {
this.interrupt.next();
publish(val);
}
https://stackblitz.com/edit/rxjs-ya93fb

Use the race operator:
The first observable to complete becomes the only observable subscribed to, so this recursive function will complete after one emission take(1), then resubscribe () => this.raceRecursive().
private timed$ = new Subject<string>();
private event$ = new Subject<string>();
ngOnInit() {
this.raceRecursive()
}
raceRecursive() {
race(
this.timed$.pipe(debounceTime(1000)),
this.event$
)
.pipe(take(1)) // force it to complete
.subscribe(
val => console.log(val), // srv call here
err => console.error(err),
() => this.raceRecursive() // reset it once complete
)
}
doChange(val: string) {
this.timed$.next(val)
}
doImmediateChange(val: string) {
this.event$.next(val)
}

You can achieve this behavior using debounce and race:
with the code you provided
private destroy$ = new Subject<void>();
private immediate$ = new Subject<void>();
private updateSubject$ = new Subject<string>();
constructor(private srv: PubSubService) {}
ngOnInit() {
this.updateSubject$.pipe(
takeUntil(this.destroy$),
debounce(() => race(timer(3000), this.immediate$))
).subscribe(val => {
this.srv.publishChange(val);
});
}
doChange(val: string, immediate?: boolean) {
this.updateSubject$.next(val);
if (immediate) this.immediate$.next();
}
// don't forget to unsubscribe
ngOnDestroy() {
this.destroy$.next();
}
emitting an immediate change will replace the previous normal change (that is debounced for 3s) without the delay (thanks to our race observable).
here's a working example

You could supply a value specific debounce time with every value and use debounce with timer to change the debounce time for values dynamically.
private updateSubject = new Subject<{ value: any, debounceTime: number}>();
ngOnInit() {
updateSubject.pipe(
debounce(({ debounceTime }) => timer(debounceTime)),
pluck('value')
).subscribe(val => publish(val));
}
doChange(value: string) {
updateSubject.next({ value, debounceTime: 3000 });
}
doImmediateChange(value: string) {
updateSubject.next({ value, debounceTime: 0 });
}
This doesn't directly stop the debounced Observable but let's you "overwrite" a waiting value with a new one being emitted with zero delay.
https://stackblitz.com/edit/rxjs-j15zyq
(user733421 didn't seem to want to add a complete solution so I expanded the approach)

The value for debounceTime is only evaluated once, on observable creation time.
To be able to dynamically update debounceTime, use debounce together with timer, like this :
debounce(()=>timer(this.debounceTime)),

Related

RXjs: reset true value after period of time

I have such service/state:
export class SpinnerService {
public throttleTime: number = 10;
public isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor() {}
public showLoader(): void {
this.isLoading$.next(true);
}
public hideLoader(): void {
this.isLoading$.next(false);
}
public get isLoadingAPIVal$(): Observable<boolean> {
return this.isLoading$.pipe(throttleTime(this.throttleTime), shareReplay());
}
}
Basically - here I store & get if I need to show app loading animation. I can set this value in multiple components in the same time or with any delays. For example I set isLoading$ to true in one component, and after 0.004sec in another. And everything works fine. Except one case.
Sometimes I need to set isLoading$ to false after it's last true value was set > 20 seconds from now.
How can I reset it to false after it was set to true last time and after 20sec?
I tried so:
constructor() {
this.isLoading$
.pipe(
filter((val) => !!val),
timeout(20000),
)
.subscribe(() => {
this.isLoading$.next(false);
});
}
but looks that it's not working and it takes first true value.
What you probably want to do is:
public get isLoadingAPIVal$(): Observable<boolean> {
return merge(
this.isLoading$,
this.isLoading$.pipe(
debounceTime(20000),
map(() => false),
),
).pipe(
throttleTime(this.throttleTime),
shareReplay(),
);
}
What it does is:
debounce the signals from isLoading$, to get a new signal 20 seconds after isLoading$ last emitted anything:
this.isLoading$.pipe(
debounceTime(20000),
emit false values then:
map(() => false),
merge your original stream of signals, and the final debounced "false" answer 20 seconds later together :)
merge(
this.isLoading$,
this.isLoading$.pipe(
debounceTime(20000),
map(() => false),
),
)

Observable subject event listener

I was looking into Observables and their differences to EventEmitter and then stumbled upon Subjects ( which I can see Angulars EventEmitter is based off ).
It seems Observables are unicast vs Subjects that are multicast ( and then an EE is simply a subject that wraps .next in emit to give the correct interface ).
Observables seem easy enough to implement
class Observable {
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(next, complete, error) {
const observer = new Observer(next, complete, error);
// return way to unsubscribe
return this._subscribe(observer);
}
}
Where Observer is just a wrapper that adds some try catches and monitors isComplete so it can clean up and stop observing.
For a Subject I came up with:
class Subject {
subscribers = new Set();
constructor() {
this.observable = new Observable(observer => {
this.observer = observer;
});
this.observable.subscribe((...args) => {
this.subscribers.forEach(sub => sub(...args))
});
}
subscribe(subscriber) {
this.subscribers.add(subscriber);
}
emit(...args) {
this.observer.next(...args);
}
}
which sort of merges into an EventEmitter with it wrapping .next with emit - but capturing the observe argument of the Observable seems wrong - and like I have just hacked up a solution. What would be the better way to produce a Subject (multicast) from an Observable (unicast)?
I tried looking at RXJS but I can't see how it's subscribers array ever gets populated :/
I think you can have a better understanding by using the debugger as well. Open a StackBlitz RxJS project, create the simplest example(depending on what you're trying to understand) and then place some breakpoints. AFAIK, with StackBlitz you can debug the TypeScript files, which seems great.
Firstly, the Subject class extends Observable:
export class Subject<T> extends Observable<T> implements SubscriptionLike { /* ... */ }
Now let's examine the Observable class.
It has the well-known pipe method:
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
where pipeFromArray is defined as follows:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
Before clarifying what's going on in the above snippet, it is important to know that operators are. An operator is a function which returns another function whose single argument is an Observable<T> and whose return type is an Observable<R>. Sometimes, T and R can be the same(e.g when using filter(), debounceTime()...).
For example, map is defined like this:
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate((source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
export function operate<T, R>(
init: (liftedSource: Observable<T>, subscriber: Subscriber<R>) => (() => void) | void
): OperatorFunction<T, R> {
return (source: Observable<T>) => {
if (hasLift(source)) {
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
}
So, operate will return a function. Notice its argument: source: Observable<T>. The return type is derived from Subscriber<R>.
Observable.lift just creates a new Observable. It's like creating nodes within a liked list.
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
// it's important to keep track of the source !
observable.source = this;
observable.operator = operator;
return observable;
}
So, an operator(like map) will return a function. What invokes that function is the pipeFromArray function:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
// here the functions returned by the operators are being called
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
In the above snippet, fn is what the operate function returns:
return (source: Observable<T>) => {
if (hasLift(source)) { // has `lift` method
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
Maybe it would be better to see an example as well. I'd recommend trying this yourself with a debugger.
const src$ = new Observable(subscriber => {subscriber.next(1), subscriber.complete()});
The subscriber => {} callback fn will be assigned to the Observable._subscribe property.
constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Next, let's try adding an operator:
const src2$ = src$.pipe(map(num => num ** 2))
In this case, it will invoke this block from pipeFromArray:
// `pipeFromArray`
if (fns.length === 1) {
return fns[0];
}
// `Observable.pipe`
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
So, the Observable.pipe will invoke (source: Observable<T>) => { ... }, where source is the src$ Observable. By invoking that function(whose result is stored in src2$), it will also call the Observable.lift method.
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
/* ... */
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
observable.source = this;
observable.operator = operator;
return observable;
}
At this point, src$ is an Observable instance, which has the source set to src$ and the operator set to function (this: Subscriber<R>, liftedSource: Observable<T>) ....
From my perspective, it's all about linked lists. When creating the Observable chain(by adding operators), the list is created from top to bottom.
When the tail node has its subscribe method called, another list will be created, this time from bottom to top. I like to call the first one the Observable list and the second one the Subscribers list.
src2$.subscribe(console.log)
This is what happens when the subscribe method is called:
const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
const { operator, source } = this;
subscriber.add(
operator
? operator.call(subscriber, source)
: source || config.useDeprecatedSynchronousErrorHandling
? this._subscribe(subscriber)
: this._trySubscribe(subscriber)
);
return subscriber;
In this case src2$ has an operator, so it will call that. operator is defined as:
function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
}
where init depends on the operator that is used. Once again, here is map's init
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate( /* THIS IS `init()` */(source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
source is in fact src$. When source.subscribe() is called, it will end up calling the callback provided to new Observable(subscriber => { ... }). Calling subscriber.next(1) will call the (value: T) => { ... } from above, which will call subscriber.next(project.call(thisArg, value, index++));(project - the callback provided to map). Lastly, subscriber.next refers to console.log.
Coming back to Subject, this is what happens when the _subscribe method is called:
protected _subscribe(subscriber: Subscriber<T>): Subscription {
this._throwIfClosed(); // if unsubscribed
this._checkFinalizedStatuses(subscriber); // `error` or `complete` notifications
return this._innerSubscribe(subscriber);
}
protected _innerSubscribe(subscriber: Subscriber<any>) {
const { hasError, isStopped, observers } = this;
return hasError || isStopped
? EMPTY_SUBSCRIPTION
: (observers.push(subscriber), new Subscription(() => arrRemove(this.observers, subscriber)));
}
So, this is how Subject's list of subscribers are is populated. By returning new Subscription(() => arrRemove(this.observers, subscriber)), it ensures that then subscriber unsubscribes(due to complete/error notifications or simply subscriber.unsubscribe()), the inactive subscriber will be removed from the Subject's list.

How to call a http Observable inside a subscription to another Observable?

I have a subject that is subscribed to and fires when a user searches.
let searchView;
this.searchSubject
.switchMap((view: any) => {
searchView = view;
this.http.post(this.url, view);
})
.subscribe(page => {
this.searchHistoryService.addRecentSearch(searchView).subscribe();
})
searchHistoryService.addRecentSearch records this search so the user can see their recent searches.
I don't think this is good practice as the observable is subscribed to everytime, I would rather use a subject which I'm calling .next() on, or combine the history call with the search call itself.
If searchHistoryService.addRecentSearch returns a Subject I can call .next() but where would I subscribe to it?
I tried adding this in the searchHistoryService's constructor
this.searchHistorySubject.do(observableIWantToCall()).subscribe()
and then replacing the subscription to 'addRecentSearch' with this:
this.searchHistoryService.searchHistorySubject.next(searchView)
But it doesnt work.
The inner observable, observableIWantToCall() gets called but the observable returned isnt subscribed to.
What's wrong with this and what is best practice for subscribing to an observable when another is finished emitting?
I think you can do something like this:
let searchView;
private searchHistorySubject$: Subject<any> = new Subject<any>();
constructor(){
this.searchHistoryService.addRecentSearch(searchView).first().subscribe(
response => {
//It will entry when you send data through next
},
error => {
console.log(error);
}
);
}
...
addRecentSearch(searchView) {
...
return this._searchHistorySubject$.asObservable();
}
setSearchHistoryEvent(value: any) {
this._searchHistorySubject$.next(value);
}
this.searchSubject
.switchMap((view: any) => {
searchView = view;
this.http.post(this.url, view);
})
.subscribe(page => {
this.searchHistoryService.setSearchHistoryEvent(searchView);
}
)

Unsubscribe all listeners from Observable

I'm new to Observables and Typescript so this might be a rookie question. I want to build a simple timer but I want to be able to unsubscribe all subscribers within the timer itself.
My code so far, looks like this.
import { Observable } from "rxjs/Rx";
export class Timer {
private interval: number;
private ticker: Observable<any>;
constructor() {
this.interval = 1000; // Miliseconds
this.ticker = Observable.interval(this.interval).timeInterval();
}
complete() {
// Unsubscribe all listeners
}
}
How can I unsubsribe all listeners from complete method?
You can't unsubscribe observers yourself if you don't have their Subscription objects (returned from .subscribe() calls).
However, you can do it the other way around and instead complete the source Observable using the takeUntil operator which will dispose the chain and unsubscribe all observers.
constructor() {
this.end = new Subject();
this.ticker = Observable.interval(this.interval)
.takeUntil(this.end)
.timeInterval();
}
...
complete() {
this.end.next();
}

Angular2: how to initially trigger Control.valueChanges

constructor(private _service: LocatorService) {
this.counties = this.countyTerm.valueChanges
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));
}
counties: Observable<County[]>;
countyTerm = new Control();
As expected, this.counties is only populated once a value is entered into the countyTerm bound control.
How can I trigger valueChanges when this component is instantiated so that the set of counties is loaded initially?
I tried the following, but it had no effect (implements OnInit was added to the class):
ngOnInit() {
this.countyTerm.updateValue('', { emitEvent: true });
}
Just start your stream out with a fixed value. Something like this:
this.counties = Rx.Observable.of('')
.concat(this.countyTerm.valueChanges.debounceTime(300))
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));
Use startWith RxJS operator to emit something before stream.
this.counties = this.countyTerm.valueChanges
.startwith('')
.debounceTime(300)
.distinctUntilChanged()
.switchMap((term: string) => _service.getCounties(term));
http://reactivex.io/documentation/operators/startwith.html
With RxJS 6 / 7 new syntax:
this.counties$ = this.countyTerm.valueChanges.pipe(
startWith(''),
debounceTime(300),
distinctUntilChanged(),
switchMap((term: string) => _service.getCounties(term))
);

Categories