How to unsubscribe from an RxJS 5 observable? - javascript

I have been using this pattern:
func myObservable() Observable<boolean> {
...
}
func myFunc() {
myObservable().subscribe((cond: boolean) => {
if (cond) {
// How do I unsubscribe here?
}
});
}
However I can't see any way to unsubscribe thereby maybe creating a memory leak.
The reason I ask is because Angular 2's HTTP client uses the same pattern - although I believe it auto-unsubscribes somehow and I would like to do the same.

You should do something like this:
func myFunc() {
var subscription = myObservable().subscribe((cond: boolean) => {
if (cond) {
// How do I unsubscribe here?
subscription.unsubscribe()
}
});
}

Related

Angular wait for all subscriptions to complete

In Angular a page makes multiple http calls on multiple actions, let's say button clicks. But when the last "DONE" button is pressed I want to make sure that all those requests are finished before it progresses. I tried to use forkJoin with observables but it triggers requests itself which is not what I want to do, I want other actions to trigger requests and just to make sure that async requests are finished when "DONE" is clicked. With promises I would just push promises to array and then do Promise.all(allRequests).then(()=>{})
observables: Observable<any>[];
onBtn1Click(){
let o1 = this.service.doAction1();
this.observables.push(o1);
o1.subscribe(resp => {
//do action1
});
}
onBtn2Click(){
let o2 = this.service.doAction2();
this.observables.push(o2);
o2.subscribe(resp => {
//do action2
});
}
onDoneClick(){
// I would like something like this just that it wouldn't trigger the requests but make sure they are completed.
forkJoin(this.observables).subscribe(()=>{
//Proceed with other things
});
}
Unless someone comes up with an elegant approach, the following should do it.
I'm creating an object to hold hot observable for each cold observable from the HTTP request. The request would emit to it's corresponding hot observable using RxJS finalize operator. These hot observables could then be combined using forkJoin with a take(1) to wait for the source requests to complete.
private httpReqs: { [key: string]: ReplaySubject<boolean> } = Object.create(null);
onBtn1Click() {
this.httpReqs['btn1'] = new ReplaySubject<boolean>(1);
this.service.doAction1().pipe(
finalize(() => this.httpReqs['btn1'].next(true))
).subscribe(resp => {
// do action1
});
}
onBtn2Click() {
this.httpReqs['btn2'] = new ReplaySubject<boolean>(1);
this.service.doAction1().pipe(
finalize(() => this.httpReqs['btn2'].next(true))
).subscribe(resp => {
// do action2
});
}
onDoneClick(){
forkJoin(
Object.values(this.httpReqs).map(repSub =>
repSub.asObservable().pipe(
take(1)
)
)
).subscribe(() => {
// Proceed with other things
});
}
Using shareReplay
If you multicast, any subscriber who subscribes to a completed stream gets the complete notification. You can leverage that.
The various share operators have an implicit refCount that changes its default every few RxJS versions. The current version for shareReplay(n) is pretty intuitive, but you may need to set refCount:false on older versions, or even use multicast(new ReplaySubject(1)), refCount()
onBtn1Click(){
let o1 = this.service.doAction1().pipe(
shareReplay(1)
);
this.observables.push(o1);
o1.subscribe(resp => {
//do action1
});
}
This is the smallest change that should get your code working the way you'd like
Scan to count activity
You can avoid forkJoin entirely if you just count currently active operations.
count = (() => {
const cc = new BehaviorSubject<number>(0);
return {
start: () => cc.next(1),
stop: () => cc.next(-1),
value$: cc.pipe(
scan((acc, curr) => acc + curr, 0)
)
}
})();
onBtn1Click(){
this.count.start();
this.service.doAction1().pipe(
finalize(this.count.stop)
).subscribe(resp => {
//do action1
});
}
onDoneClick(){
this.count.value$.pipe(
first(v => v === 0) // Wait until nothing is currently active
).subscribe(() => {
//Proceed with other things
});
}

Avoid loading event chains in javascript

I have a problem in my code where many entities have async loading procedures and can't be used until these are complete. There are chains of these dependencies.
So A->B->C where A needs B needs C.
I have written code like
class B{
constructor(callback){
this.loaded=false
this.load(callback)
}
load(callback){
...do stuff
this.loaded=true
callback()
}
}
class A{
constructor(){
this.loaded=false
this.b=new B(()=>{this.loaded=true})
}
}
This seems really bad. Can anyone suggest a better solution?
Usually, it is a bad practice to perform async task directly in constructor (as stated here). With this taken in account, you can follow VLAZ advice and start using promises.
You would then have something like this:
class B {
constructor() {
this.loaded = false
}
load() {
return new Promise((resolve => {
this.loaded = true;
// do stuff
return resolve()
}))
}
}
class A {
constructor() {
this.loaded = false
}
load() {
return new Promise(resolve => {
this.b = new B()
this.loaded = true
return resolve(this.b)
})
}
}
// Use it like this
const a = new A()
a.load()
.then(b => b.load())
.then(/* and so on */)

Retry (resubscribe) to source observable if no next(...) call within a timeout

I am trying to take a rxjs source observable, representing a network connection that pushes me data, and reconnect (by resubscribing to the source observable) if I have not received data within a timeout period. I can certainly write this in a somewhat hacky way, but is there a good way to write this concisely with rxjs?
I ultimately wrote an operator. I think there is a better way to do this, but seeing as how no one else has an idea either, here's the pipeable operator that I wrote:
import { Observable, Subscription } from "rxjs";
export function retryAfterTimeout<T>(timeout: number, allowCompletion = false): (obs: Observable<T>) => Observable<T> {
return source => new Observable<T>(observer => {
let sub: Subscription | undefined;
let timer: number | undefined;
function resetTimer() {
if (timer) clearTimeout(timer);
timer = window.setTimeout(() => resub(), timeout);
}
function resub() {
if (sub) sub.unsubscribe();
sub = source.subscribe({
next(x) {
resetTimer();
observer.next(x);
},
error(err) {
observer.error(err);
},
complete() {
if (allowCompletion)
observer.complete();
else
resub();
}
});
}
resub();
resetTimer();
return () => {
if (sub) sub.unsubscribe();
if (timer) window.clearTimeout(timer);
};
});
}

Subscribe to Observable in Angular2

I have a function, which remembers a previous url
prevId () {
let name, id, lat, lng;
this.router.events
.filter(event => event instanceof NavigationEnd)
.subscribe(e => {
console.log('prev:', this.previousUrl);
this.previousUrl = (e as NavigationEnd).url;
}
I would like to rewrite this in Observable-style, if it possible, but everything I try doesn't work.
Is it possible to solve?
Could you also please advice me some good articles about Observable, because I am just beginner and the topic seems to be pretty difficult.
UPD: I need to usr the data outside the function later, that is why I need Observer.
for example
myFun(a) {
console.log(a);
}
myFun(this.previousUrl);
If the only thing you need is use the url outside of your function, you may do this:
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/take';
prevId(): Observable<NavigationEnd> {
return new Observable(observer => {
this.router.events
.filter(event => event instanceof NavigationEnd)
.take(1)
.subscribe(e => {
observer.next(e as NavigationEnd);
observer.complete();
});
});
}
Then you must call the function like this:
prevId().subscribe(navigationEndEvent => {
this.previousUrl = navigationEndEvent.url;
});
I made this example thinking that you just want the previousUrl information when you call the prevId(), because of this I put the .take(1) operator and called observer.complete().
The .take(1) will automatically unsubscribe the subscription made into this.router.event when the first value comes from it.
And the observer.complete() will also unsubscribe the all the subscriptions made whenever in the returned Observable.

How to use an observable in angular 2 guards' canActivate()

I have created an authentication guard for my angular2 rc5 application.
I am also using a redux store. In that store I keep the user's authentication state.
I read that the guard can return an observable or promise (https://angular.io/docs/ts/latest/guide/router.html#!#guards)
I can't seem to find a way for the guard to wait until the store/observable is updated and only after that update return the guard because the default value of the store will always be false.
First try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// updated after a while ->
this.authenticated$.subscribe((auth) => {
// will only reach here after the first update of the store
if (auth) { resolve(true); }
// it will always reject because the default value
// is always false and it takes time to update the store
reject(false);
});
});
}
}
Second try:
#Injectable()
export class AuthGuard implements CanActivate {
#select(['user', 'authenticated']) authenticated$: Observable<boolean>;
constructor() {}
canActivate(): Promise<boolean> {
return new Promise((resolve, reject) => {
// tried to convert it for single read since canActivate is called every time. So I actually don't want to subscribe here.
let auth = this.authenticated$.toPromise();
auth.then((authenticated) => {
if (authenticated) { resolve(true); }
reject(false);
});
auth.catch((err) => {
console.log(err);
});
}
}
When you subscribe to an observable, you can provide a callback function; in the example below, I call it CompleteGet. CompleteGet() will only be invoked on a successful get that returns data and not an error. You place whatever follow on logic you need in the callback function.
getCursenByDateTest(){
this.cursenService
.getCursenValueByDateTest("2016-7-30","2016-7-31")
.subscribe(p => {
this.cursens = p;
console.log(p)
console.log(this.cursens.length);
},
error => this.error = error,
() => this.CompleteGet());
}
completeGet() {
// the rest of your logic here - only executes on obtaining result.
}
I believe you can also add a .do() to the observable subscription to accomplish the same thing.
all you need to do is force the observable to update:
canActivate(): Observable<boolean> {
return this.authenticated$.take(1);
}
Edit:
canActivate waits for the source observable to complete, and (most likely, I don't know what happens behind the scenes), the authenticated$ observable emits .next(), not .complete()
From documentation: http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-take
.take(1) method takes first value emitted by the source observable and then completes
Edit2:
I just looked at snippet you pasted, and I was right - the store.select() observable never completes, it always emits .next
Subscribe doesn't return an Observable.
However, you can use the map operator like that:
this.authenticated$.map(
authenticated => {
if(authenticated) {
return true;
}
return false;
}
).first() // or .take(1) to complete on the first event emit

Categories

Resources