In my Angular 2 app i have many observables and subscriptions. Ofcourse I should unsubscribe when I leave the page, but i\'m trying to find out if it\'s possible to get the
Probably a bit late, but you could leverage the help of rxjs-spy.
This solution is equivalent the proposed one, and, in my opinion, better maintainable.
I usually enable it globally in main.ts as a fire and forget strategy. You simply have to:
import { create } from 'rxjs-spy';
initialize the rxjs-spy in debug builds right after the angular initialization snippet:
if (environment.production) {
enableProdMode();
}
else {
//we enable RXjs Spy on non production bulds only
const spy = create();
// we call show for two purposes: first is to log to the console an empty snapshot so we can see that everything is working as expected, then to suppress unused variable usage (the latter is a convention on mine)
spy.show();
}
give your observables a name:
import { tag } from 'rxjs-spy/operators';
...
// This is a sample method which asks for a "Product" entity. Product and this.http is omitted as the focus is on tagging the observable
public getProductById(productId: number): Observable<Product> {
let params = new HttpParams()
.append('productId', productId.toString())
;
// we tag the returned observable with the name 'getProductById' (this is a convention on mine, you can choose whatsoever name)
return this.http.get<Product>(this.baseUrl + "api/product", { params: params }).pipe(tag("getProductById"));
}
when you need to look at rxjs state, you can simply open the console window and use rxSpy.show()
to have the current snapshot
You may use additional commands. A very detailed tutorial is Debugging with rxjs Spy.
(I'd be very glad if somebody reading this answer manages to fix the formatting as I cannot have it properly within the list)
The following utility function may help...
function subscriberCount<T>(sourceObservable: Observable<T>, description: string) {
let counter = 0;
return Observable.create((subscriber: Subscriber<T>) => {
const subscription = sourceObservable.subscribe(subscriber);
counter++;
console.log(`${description} subscriptions: ${counter}`);
return () => {
subscription.unsubscribe();
counter--;
console.log(`${description} subscriptions: ${counter}`);
}
});
}
Use it like this:
const timer$ = subscriberCount(Observable.timer(1000), 'Timer');
And the console will log whenever the subscriber count changes
In your case you could make good use of RxJS Subjects with refCounting. You would give the Subject your source observable and let refCount manage its subscriptions. refCount will unsubscribe from your source observable if there are no observers listening to it. On the other hand it creates new instance of source observable if observer count was 0 and an observer subscribed to it.
Subject would act as a proxy between the observer(s) and the source observable and manage subscribing and unsubscribing from it. In essence how it would work is when you have your first observer subscribe to your Subject the Subject in turn subscribes to your source observable (refCount went from 0 to 1). Subjects allow multiple observers listening to the unicast source observable making it multicast. When observers start to unsubscribe and the refCount falls down to 0 again the Subject itself will unsubscribe from source observable.
It's better understood in code:
const {
Observable
} = Rx;
let sourceObservable = Observable
.create((observer) => {
let count = 0;
let interval = setInterval(() => {
observer.next(count++)
}, 700);
setTimeout(() => {
clearInterval(interval);
observer.complete();
}, 5500);
return () => {
clearInterval(interval);
console.log('######## Source observable unsubscribed');
}
})
.do((x) => console.log('#### Source emits: ' + x));
let subject = sourceObservable
.share()
//.do((x) => console.log('#### Subject emits: ' + x))
;
let pageOneObserver;
let pageTwoObserver;
let pageThreeObserver;
setTimeout(() => {
console.log('pageOneObserver will subscribe');
pageOneObserver = subject.subscribe({
next: (x) => {
console.log('pageOneObserver gets: ' + x);
},
complete: () => {
console.log('pageOneObserver: complete');
}
});
}, 1000);
setTimeout(() => {
console.log('pageTwoObserver will subscribe');
pageTwoObserver = subject.subscribe({
next: (x) => {
console.log('pageTwoObserver gets: ' + x);
},
complete: () => {
console.log('pageTwoObserver: complete');
}
});
}, 4000);
setTimeout(() => {
console.log('pageOneObserver will unsubscribe');
pageOneObserver.unsubscribe();
}, 7000);
setTimeout(() => {
console.log('pageTwoObserver will unsubscribe');
pageTwoObserver.unsubscribe();
}, 10000);
setTimeout(() => {
console.log('pageThreeObserver will subscribe');
pageThreeObserver = subject.subscribe({
next: (x) => {
console.log('pageThreeObserver gets: ' + x);
},
complete: () => {
console.log('pageThreeObserver: complete');
}
});
}, 13000);
setTimeout(() => {
console.log('pageThreeObserver will unsubscribe');
pageThreeObserver.unsubscribe();
}, 16000);
<script src="https://unpkg.com/rxjs@5.1.1/bundles/Rx.min.js"></script>
There are some shorthand ways of creating subjects. For example:
sourceObservable.share();
// is the same as
sourceObservable.publish().refCount();
sourceObservable.publish().refCount();
// is the same as
sourceObservable.multicast(new Rx.Subject()).refCount();
sourceObservable.publishReplay().refCount();
// is the same as
sourceObservable.multicast(new Rx.ReplaySubject(1)).refCount();
sourceObservable.publishBehavior().refCount();
// is the same as
sourceObservable.multicast(new Rx.BehaviorSubject(0)).refCount();
sourceObservable.publishLast().refCount();
// is the same as
sourceObservable.multicast(new Rx.AsyncSubject()).refCount();
sourceObservable.share();
has also built in subject factory which means when the source observable is complete at some point we have to create a new instance of the sourceObservable but that can be done with only a new instance of the subject of your choice. Using the other available subjects below we have to explicitly return a factory function to the multicast operator.
When you would like to use other subject types besides the Rx.Subject() and wanting to make your observable subscription truly reusable you have to use subject factories (which is just a function that return a new instance of any subject you like to use) which is illustrated below:
const {
Observable
} = Rx;
let sourceObservable = Observable
.create((observer) => {
let count = 0;
let interval = setInterval(() => {
observer.next(count++)
}, 700);
setTimeout(() => {
clearInterval(interval);
observer.complete();
}, 5500);
return () => {
clearInterval(interval);
console.log('######## Source observable unsubscribed');
}
})
.do((x) => console.log('#### Source emits: ' + x));
/* You could return whatever subject instance you like here */
let subjectFactory = () => new Rx.ReplaySubject(1);
let subject = sourceObservable
.multicast(subjectFactory)
.refCount();
//.do((x) => console.log('#### Subject emits: ' + x))
;
let pageOneObserver;
let pageTwoObserver;
let pageThreeObserver;
setTimeout(() => {
console.log('pageOneObserver will subscribe');
pageOneObserver = subject.subscribe({
next: (x) => {
console.log('pageOneObserver gets: ' + x);
},
complete: () => {
console.log('pageOneObserver: complete');
}
});
}, 1000);
setTimeout(() => {
console.log('pageTwoObserver will subscribe');
pageTwoObserver = subject.subscribe({
next: (x) => {
console.log('pageTwoObserver gets: ' + x);
},
complete: () => {
console.log('pageTwoObserver: complete');
}
});
}, 4000);
setTimeout(() => {
console.log('pageOneObserver will unsubscribe');
pageOneObserver.unsubscribe();
}, 7000);
setTimeout(() => {
console.log('pageTwoObserver will unsubscribe');
pageTwoObserver.unsubscribe();
}, 10000);
setTimeout(() => {
console.log('pageThreeObserver will subscribe');
pageThreeObserver = subject.subscribe({
next: (x) => {
console.log('pageThreeObserver gets: ' + x);
},
complete: () => {
console.log('pageThreeObserver: complete');
}
});
}, 13000);
setTimeout(() => {
console.log('pageThreeObserver will unsubscribe');
pageThreeObserver.unsubscribe();
}, 16000);
<script src="https://unpkg.com/rxjs@5.1.1/bundles/Rx.min.js"></script>
Feel free to ask anything if there is still something unclear.