The situation at hand:
I currently have a service that continuously polls a specific URL every 2 seconds:
export class FooDataService {
...
public provideFooData() {
const interval = Observable.interval(2000).startWith(0);
return interval
.switchMap(() => this.http.get(this.requestUrl))
.map(fooData => fooData.json())
}
}
Now, in one of my components I need to display this data obtained through polling:
export class FooComponent implements OnInit {
constructor(private fooDataService: FooDataService) {}
public fooData$: Observable<FooData>;
ngOnInit() {
this.fooData$ = this.fooDataService.provideFooData();
}
}
Within the component template, I utilize an async pipe to retrieve the polled data and pass it to a sub-component:
<foo-data-viewer [data]="fooData$ | async"></foo-data-viewer>
An issue with the current setup:
The way this functionality is implemented makes it challenging to test using protractor (refer to my previous question on the topic or this article). Due to all code being executed within the ngZone, protractor will wait for all queued actions to complete before proceeding. However, Observable.interval() queues an indefinite number of actions, resulting in protractor timing out.
A commonly suggested solution:
The most frequently recommended approach involves utilizing runOutsideAngular like so:
export class FooComponent implements OnInit, OnDestroy {
constructor(private ngZone: NgZone,
private fooDataService: FooDataService) {}
public fooData: FooData;
public fooDataSubscription: Subscription<FooData>;
ngOnInit() {
this.ngZone.runOutsideAngular(() => {
this.fooDataSubscription =
this.fooDataService.provideFooData()
.subscribe(
fooData => this.ngZone.run(() => this.fooData = fooData)
);
});
}
ngOnDestroy(): void {
this.fooDataSubscription.unsubscribe();
}
}
Running the interval outside of the ngZone prevents protractor from waiting for the polling process to finish, thereby preventing timeouts.
However, this approach has its drawbacks:
- Unable to use the async pipe; manual subscription management becomes necessary.
- Manual management and cleanup of subscriptions when the component is destroyed.
- Loss of the clean and elegant Observable-style, leading to convoluted code - especially with multiple polling services.
The inquiry at hand:
Is there a feasible way to retain the functional rxjs style while still working with the async pipe (or similar) when running the interval outside of the ngZone?
I came across this GitHub project which seems to offer a solution fitting my requirements, but I encountered difficulties implementing it.
I am looking for a practical example of exiting and reentering the zone in my scenario without the need to manually handle subscriptions.