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();
}
Related
Angular's Location service has a method onUrlChange that registers url events that popstate or hashchange don't, and I need that for a part of my project.
/**
* Registers a URL change listener. Use to catch updates performed by the Angular
* framework that are not detectible through "popstate" or "hashchange" events.
*
* #param fn The change handler function, which take a URL and a location history state.
*/
onUrlChange(fn: (url: string, state: unknown) => void) {
this._urlChangeListeners.push(fn);
this.subscribe(v => { this._notifyUrlChangeListeners(v.url, v.state); });
}
Other than usually, there's no subscription returned, so we can't unsubscribe on destroy. The listener is still intact after navigating away from the route that needs to listen to those events.
My ugly hack for the moment is to filter Locations private _urlChangeListeners onDestroy, but that relies on String(fn) !== '(change) => this.urlFileHandler(change)' and clearly isn't a nice way.
Is there any other possibility to remove that listener from the listeners?
Not really an answer to the question but I decided to subscribe to it once and use an observable. For example:
import { Location } from '#angular/common';
export class MyService {
public urlChanged = new Subject();
constructor(private location: Location) {
// This is a shared service so the code only gets called once
location.onUrlChange((url, state) => {
this.urlChanged.next({ url, state });
});
}
}
Then subscribe to it in the normal way, for example:
private sub;
ngOnInit() {
this.sub = this.myService.urlChanged.subscribe(e => {
//Do stuff
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
Since I only use one event suscription, I used this when I need to remove it:
(this.location as any)._urlChangeListeners = [];
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)),
Our AngularJS project had start it's long way to the modern Angular.
The ngMigration util recommend me to remove all the $rootScope dependecies because Angular doesn't contain a similar concept like $rootScope. It is pretty simple in some cases but I don't know what to do with event subscription mechanisms.
For example I have a some kind of Idle watchdog:
angular
.module('myModule')
//...
.run(run)
//...
function run($rootScope, $transitions, Idle) {
$transitions.onSuccess({}, function(transition) {
//...
Idle.watch(); // starts watching for idleness
});
$rootScope.$on('IdleStart', function() {
//...
});
$rootScope.$on('IdleTimeout', function() {
logout();
});
}
On which object instead of $rootScope I have to call the $on function if I want to get rid of the $rootScope?
UPD
The question was not about "how to migrate on Angular2 event system". It was about how to remove a $rootScope dependencies but keep a event system. Well it seems to be impossible.
I don't know what to do with event subscription mechanisms.
Angular 2+ frameworks replace the $scope/$rootScope event bus with observables.
From the Docs:
Transmitting data between components
Angular provides an EventEmitter class that is used when publishing values from a component. EventEmitter extends RxJS Subject, adding an emit() method so it can send arbitrary values. When you call emit(), it passes the emitted value to the next() method of any subscribed observer.
A good example of usage can be found in the EventEmitter documentation.
For more information, see
Angular Developer Guide - Observables in Angular
You can implement TimeOutService which will do the log out after x minutes (in this case 15 min) of inactivity or it will reset the timer after certain action(s).
import { Injectable, OnDestroy } from '#angular/core';
import { Router } from '#angular/router';
import { Observable, Subject, Subscription, timer } from 'rxjs';
import { startWith, switchMap } from 'rxjs/operators';
import { AuthService } from 'path/to/auth.service';
#Injectable()
export class TimeoutService implements OnDestroy {
limitMinutes = 15;
secondsLimit: number = this.limitMinutes * 60;
private reset$ = new Subject();
timer$: Observable<any>;
subscription: Subscription;
constructor(private router: Router,
private authService: AuthService,
) {
}
startTimer() {
this.timer$ = this.reset$.pipe(
startWith(0),
switchMap(() => timer(0, 1000))
);
this.subscription = this.timer$.subscribe((res) => {
if (res === this.secondsLimit) {
this.logout();
}
});
}
resetTimer() {
this.reset$.next(void 0);
}
endTimer() {
if (typeof this.subscription !== 'undefined') {
this.subscription.unsubscribe();
}
}
logout(): boolean {
this.authService.signOut().subscribe((res) => {
this.subscription.unsubscribe();
});
return false;
}
ngOnDestroy():void {
this.subscription.unsubscribe();
}
}
And in the AppComponent have listener which will reset timeout on certain actions
In case as bellow it listens for keyboard strokes, mouse wheel, or mouse click
constructor(
private timeoutService: TimeoutService
) {
}
#HostListener('document:keyup', ['$event'])
#HostListener('document:click', ['$event'])
#HostListener('document:wheel', ['$event'])
resetTimer () {
this.timeoutService.resetTimer();
}
When subscribing to changes in an Angular Abstract Control using valueChanges, is it necessary to unsubscribe()?
I often do this:
// this.form is a FormGroup within a Component.
this.form.valueChanges.subscribe(_ => {
console.log(this.form.value);
});
But should I be managing the subscription myself (as I do with ngrx in general)?:
import { Subscription } from 'rxjs';
// this.subscription is ngrx Subscription.
this.subscription = this.form.valueChanges.subscribe(_ => {
console.log(this.form.value);
});
public ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
The only reason I have not done this previously is because tutorials, examples and documentation on Angular Forms generally omit storing a reference to the subscription, and instead, just use valueChanges as is.
Conversely, ngrx tutorials seem to highlight the importance of unsubscribing to avoid memory leaks.
Yes it is necessary, but you could use take until instead.
private unsubscribe$: Subject<void> = new Subject<void>();
this.subscription = control.valueChanges
pipe(takeUntil(this.unsubscribe$))
.subscribe(_ => {
console.log(this.form.value);
});
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87
I'll try to explain this as best I can.
I have a service that contains an observable class that performs tasks to update itself. That observable class needs to be pushed out to the app using the observer located in the service. How can I call that observer from the child without creating some sort of dependency loop?
Here is a rough example:
class MyService {
subClass$: Observable<SubClass>;
_subClassObserver: Observer<SubClass>;
constructor(private _subClassStore: SubClass){
this.subClass$ = new Observable(observer => {
this._subClassObserver = observer
}).share();
}
pushData(){
this._subClassObserver.next(this._subClassStore)
}
}
class SubClass {
displayData: string;
displayData2: number;
constructor(){
socket.on('setData', function(obj){
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
}
}
}
_subClassStore is updating through a stream coming in from socket.io. How do I let MyService know when the SubClass data changes so that it can push it using _subClassObserver.next(_subClassStore)?
EDIT:
I added more details to the example above to show how they are related and utilized.
SubClass is just a listener for a stream of data coming from socket.io and saving the information into the class. It starts listening when MyService is constructed.
The goal of MyService is to provide a bunch these sub classes that can be subscribed to across the app. Each one would allow access to a different data stream and the associated data, but all would be contained within a single service.
The question is how to call the pushData() function in the parent so that it keeps the stream updated for subscribers in the app.
Edit 2:
This might help. below is how it would be written as a service without the sub class. The only reason why I'm not doing this is because there are a substantial amount of these listeners being stored to Observables and abstracting them out into classes makes the information much easier to manage but pushing it to the app is what I can't figure out:
class MyService {
class1$: Observable<DataStream>;
_class1Observer: Observer<DataStream>;
_class1Store: DataStream;
constructor(){
this._class1store = {displayData: 'hello', displayData2: 0};
this.class1$ = new Observable(observer => {
this._class1Observer = observer
}).share();
socket.on('setData', function(obj){
this._class1Store.displayData = obj.dd1;
this._class1Store.displayData2 = obj.dd2;
this._class1Observer.next(this._class1Store)
}
}
interface DataStream = {
displayData: string;
displayData2: number;
}
Instead of function(obj) use ()=> otherwise this won't ponit to the MyService instance.
constructor(){
socket.on('setData', (obj) =>{
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
}
}
I'm not sure but I think socket is prone to run outside Angulars zone.
Try also
constructor(zone:NgZone){
socket.on('setData', (obj) =>{
zone.run(() => {
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
//How to call pushData() in MyService from here to push data to app?
});
}
}
To be able to call a method in MyService from SubClass, SubClass needs a reference to MyService
class MyService {
subClass$: Observable<SubClass>;
_subClassObserver: Observer<SubClass>;
constructor(private _subClassStore: SubClass){
_subClassStore.myService = this;
this.subClass$ = new Observable(observer => {
this._subClassObserver = observer
}).share();
}
pushData(){
this._subClassObserver.next(this._subClassStore)
}
}
class SubClass {
displayData: string;
displayData2: number;
myService:MyService;
constructor(zone:NgZone){
socket.on('setData', (obj) =>{
zone.run(() => {
this.displayData = obj.dd1;
this.displayData2 = obj.dd2;
this.myService.pushData();
});
}
}
}