I just started to write e2e tests for my app and am running into timeout problems with Protractor and ngrx/effects.
I have the following effect dispatching an action ev
For Angular 6 and RxJS 6 use the following code:
import { SchedulerLike, Subscription } from 'rxjs'
import { NgZone } from '@angular/core'
class LeaveZoneScheduler implements SchedulerLike {
constructor(private zone: NgZone, private scheduler: SchedulerLike) { }
schedule(...args: any[]): Subscription {
return this.zone.runOutsideAngular(() =>
this.scheduler.schedule.apply(this.scheduler, args)
)
}
now (): number {
return this.scheduler.now()
}
}
class EnterZoneScheduler implements SchedulerLike {
constructor(private zone: NgZone, private scheduler: SchedulerLike) { }
schedule(...args: any[]): Subscription {
return this.zone.run(() =>
this.scheduler.schedule.apply(this.scheduler, args)
)
}
now (): number {
return this.scheduler.now()
}
}
export function leaveZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
return new LeaveZoneScheduler(zone, scheduler)
}
export function enterZone(zone: NgZone, scheduler: SchedulerLike): SchedulerLike {
return new EnterZoneScheduler(zone, scheduler)
}
The effect should look like:
import { asyncScheduler } from 'rxjs'
import { filter, observeOn, bufferTime } from 'rxjs/operators'
import { enterZone, leaveZone } from './util';
actions$.ofType('[Light] Turn On')
.pipe(
bufferTime(300, leaveZone(this.ngZone, asyncScheduler)),
filter(messages => messages.length > 0),
observeOn(enterZone(this.ngZone, asyncScheduler)),
)
The solution is to schedule the timer observable to run outside of NgZone and then re-enter the zone when something interesting occurs.
First you are going to need two utility functions that wrap any scheduler and cause the effect to enter or leave the zone:
import { Subscription } from 'rxjs/Subscription';
import { Scheduler } from 'rxjs/Scheduler';
import { NgZone } from '@angular/core';
class LeaveZoneSchduler {
constructor(private zone: NgZone, private scheduler: Scheduler) { }
schedule(...args: any[]): Subscription {
return this.zone.runOutsideAngular(() =>
this.scheduler.schedule.apply(this.scheduler, args)
);
}
}
class EnterZoneScheduler {
constructor(private zone: NgZone, private scheduler: Scheduler) { }
schedule(...args: any[]): Subscription {
return this.zone.run(() =>
this.scheduler.schedule.apply(this.scheduler, args)
);
}
}
export function leaveZone(zone: NgZone, scheduler: Scheduler): Scheduler {
return new LeaveZoneSchduler(zone, scheduler) as any;
}
export function enterZone(zone: NgZone, scheduler: Scheduler): Scheduler {
return new EnterZoneScheduler(zone, scheduler) as any;
}
Then using a scheduler (like asap
or async
) you can cause a stream to enter or leave the zone:
import { async } from 'rxjs/scheduler/async';
import { enterZone, leaveZone } from './util';
actions$.ofType('[Light] Turn On')
.bufferTime(300, leaveZone(this.ngZone, async))
.filter(messages => messages.length > 0)
.observeOn(enterZone(this.ngZone, async))
Note that most of the time-based operators (like bufferTime
, debounceTime
, Observable.timer
, etc) already accept an alternative scheduler. You only need observeOn
to re-enter the zone when something interesting happens.