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),
),
)
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.
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);
}
)
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();
}
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))
);