UNPKG

3.91 kBPlain TextView Raw
1import { NgZone, Injectable, ApplicationRef, APP_INITIALIZER, Injector } from '@angular/core';
2import { Subject } from 'rxjs/Subject';
3import { async } from 'rxjs/scheduler/async';
4import 'rxjs/add/operator/debounceTime';
5import 'rxjs/add/operator/take';
6import 'rxjs/add/operator/publish';
7import 'rxjs/add/operator/observeOn';
8
9// requestIdleCallback polyfill
10export type Deadline = {didTimeout: boolean, timeRemaining: () => number};
11export type IdleCallback = (deadline: Deadline) => void;
12export type IdleOptions = {timeout: number};
13
14@Injectable()
15export class Idle {
16 idleHandlers = new Map();
17 stableObservable$;
18 constructor(public ngZone: NgZone) {
19 this.stableObservable$ = this.ngZone
20 .onStable
21 .observeOn(async)
22 .publish()
23 .refCount();
24 }
25 requestIdleCallback(callback) {
26 if ('requestIdleCallback' in window) {
27 window['requestIdleCallback'](callback);
28 } else {
29 this.polyfillRequestIdleCallback(callback);
30 }
31 }
32
33 cancelIdleCallback(handler) {
34 if ('cancelIdleCallback' in window) {
35 window['cancelIdleCallback'](handler);
36 } else {
37 this.polyfillCancelIdleCallback(handler);
38 }
39 }
40
41 polyfillCancelIdleCallback(handler) {
42 let {unsubscribe, timerId}: any = this.idleHandlers.get(handler);
43
44 if (unsubscribe) {
45 unsubscribe();
46 }
47 if (timerId) {
48 this.ngZone.runOutsideAngular(function() {
49 clearTimeout(timerId);
50 });
51 }
52
53 this.idleHandlers.delete(handler);
54 }
55
56 polyfillRequestIdleCallback(callback: IdleCallback, {timeout}: IdleOptions = {timeout: 50}) {
57 let dispose = undefined;
58 // compiling problem
59 const _self = this;
60 _self.ngZone.runOutsideAngular(() => {
61 function cb(): void {
62 const start: number = Date.now();
63 const deadline: Deadline = {
64 didTimeout: false,
65 timeRemaining() {
66 return Math.max(0, 50 - (Date.now() - start));
67 }
68 };
69
70 setTimeout(() => {
71 deadline.didTimeout = true;
72 if (dispose) {
73 dispose.unsubscribe();
74 }
75 }, timeout || 50);
76
77 callback(deadline);
78 }
79
80 if (this.ngZone.isStable) {
81 let timerId = setTimeout(cb, 10);
82 this.idleHandlers.set(callback, {
83 timerId
84 });
85 } else {
86 dispose = this.stableObservable$
87 .debounceTime(10)
88 .take(1)
89 .subscribe(
90 () => {
91 let timerId = setTimeout(cb, 10);
92 this.idleHandlers.set(callback, {
93 unsubscribe: dispose.unsubscribe,
94 timerId
95 });
96 });
97 this.idleHandlers.set(callback, {
98 unsubscribe: dispose.unsubscribe
99 });
100 }
101
102 });
103
104 }
105}
106
107export const ANGULARCLASS_IDLE_PROVIDERS = [
108 Idle
109];
110
111export function setupPrefetchInitializer(injector: Injector, callbacks?: Array<Function>) {
112 let defaultReturnValue = (): any => null;
113 if (!callbacks || !callbacks.length) {
114 return defaultReturnValue;
115 }
116 // https://github.com/angular/angular/issues/9101
117 // Delay to avoid circular dependency
118 setTimeout(() => {
119 const appRef = injector.get(ApplicationRef);
120 if (appRef.componentTypes.length === 0) {
121 appRef.registerBootstrapListener(() => {
122 let idle = injector.get(Idle);
123 callbacks.forEach((cb) => idle.requestIdleCallback(cb));
124 });
125 } else {
126 let idle = injector.get(Idle);
127 callbacks.forEach((cb) => idle.requestIdleCallback(cb));
128 }
129 }, 0);
130
131 return defaultReturnValue;
132}
133
134export function providePrefetchIdleCallbacks(prefetchCallbacks = []) {
135 return [
136 ...ANGULARCLASS_IDLE_PROVIDERS,
137 // Trigger initial navigation
138 { provide: APP_INITIALIZER, multi: true, useFactory: (injector) => {
139 return setupPrefetchInitializer(injector, prefetchCallbacks);
140 }, deps: [Injector] }
141 ];
142}