@@ -18,10 +18,31 @@ import {NgZone} from '../zone/ng_zone';
1818 */
1919export declare interface PublicTestability {
2020 isStable ( ) : boolean ;
21- whenStable ( callback : Function ) : void ;
21+ whenStable ( callback : Function , timeout ?: number , updateCallback ?: Function ) : void ;
2222 findProviders ( using: any , provider : string , exactMatch : boolean ) : any[ ] ;
2323}
2424
25+ // Angular internal, not intended for public API.
26+ export interface PendingMacrotask {
27+ source : string ;
28+ isPeriodic : boolean ;
29+ delay ?: number ;
30+ creationLocation : Error ;
31+ xhr ?: XMLHttpRequest ;
32+ }
33+
34+ // Angular internal, not intended for public API.
35+ export type DoneCallback = ( didWork : boolean , tasks ?: PendingMacrotask [ ] ) => void ;
36+ export type UpdateCallback = ( tasks : PendingMacrotask [ ] ) => boolean ;
37+
38+ interface WaitCallback {
39+ // Needs to be 'any' - setTimeout returns a number according to ES6, but
40+ // on NodeJS it returns a Timer.
41+ timeoutId : any ;
42+ doneCb : DoneCallback ;
43+ updateCb ?: UpdateCallback ;
44+ }
45+
2546/**
2647 * The Testability service provides testing hooks that can be accessed from
2748 * the browser and by services such as Protractor. Each bootstrapped Angular
@@ -30,23 +51,25 @@ export declare interface PublicTestability {
3051 */
3152@Injectable ( )
3253export class Testability implements PublicTestability {
33- /** @internal */
34- _pendingCount : number = 0 ;
35- /** @internal */
36- _isZoneStable : boolean = true ;
54+ private _pendingCount : number = 0 ;
55+ private _isZoneStable : boolean = true ;
3756 /**
3857 * Whether any work was done since the last 'whenStable' callback. This is
3958 * useful to detect if this could have potentially destabilized another
4059 * component while it is stabilizing.
4160 * @internal
4261 */
43- _didWork : boolean = false ;
44- /** @internal */
45- _callbacks : Function [ ] = [ ] ;
46- constructor ( private _ngZone : NgZone ) { this . _watchAngularEvents ( ) ; }
62+ private _didWork : boolean = false ;
63+ private _callbacks : WaitCallback [ ] = [ ] ;
4764
48- /** @internal */
49- _watchAngularEvents ( ) : void {
65+ private taskTrackingZone : any ;
66+
67+ constructor ( private _ngZone : NgZone ) {
68+ this . _watchAngularEvents ( ) ;
69+ _ngZone . run ( ( ) => { this . taskTrackingZone = Zone . current . get ( 'TaskTrackingZone' ) ; } ) ;
70+ }
71+
72+ private _watchAngularEvents ( ) : void {
5073 this . _ngZone . onUnstable . subscribe ( {
5174 next : ( ) => {
5275 this . _didWork = true ;
@@ -69,6 +92,7 @@ export class Testability implements PublicTestability {
6992
7093 /**
7194 * Increases the number of pending request
95+ * @deprecated pending requests are now tracked with zones.
7296 */
7397 increasePendingRequestCount ( ) : number {
7498 this . _pendingCount += 1 ;
@@ -78,6 +102,7 @@ export class Testability implements PublicTestability {
78102
79103 /**
80104 * Decreases the number of pending request
105+ * @deprecated pending requests are now tracked with zones
81106 */
82107 decreasePendingRequestCount ( ) : number {
83108 this . _pendingCount -= 1 ;
@@ -92,36 +117,93 @@ export class Testability implements PublicTestability {
92117 * Whether an associated application is stable
93118 */
94119 isStable ( ) : boolean {
95- return this . _isZoneStable && this . _pendingCount == 0 && ! this . _ngZone . hasPendingMacrotasks ;
120+ return this . _isZoneStable && this . _pendingCount === 0 && ! this . _ngZone . hasPendingMacrotasks ;
96121 }
97122
98- /** @internal */
99- _runCallbacksIfReady ( ) : void {
123+ private _runCallbacksIfReady ( ) : void {
100124 if ( this . isStable ( ) ) {
101125 // Schedules the call backs in a new frame so that it is always async.
102126 scheduleMicroTask ( ( ) => {
103127 while ( this . _callbacks . length !== 0 ) {
104- ( this . _callbacks . pop ( ) ! ) ( this . _didWork ) ;
128+ let cb = this . _callbacks . pop ( ) ! ;
129+ clearTimeout ( cb . timeoutId ) ;
130+ cb . doneCb ( this . _didWork ) ;
105131 }
106132 this . _didWork = false ;
107133 } ) ;
108134 } else {
109- // Not Ready
135+ // Still not stable, send updates.
136+ let pending = this . getPendingTasks ( ) ;
137+ this . _callbacks = this . _callbacks . filter ( ( cb ) => {
138+ if ( cb . updateCb && cb . updateCb ( pending ) ) {
139+ clearTimeout ( cb . timeoutId ) ;
140+ return false ;
141+ }
142+
143+ return true ;
144+ } ) ;
145+
110146 this . _didWork = true ;
111147 }
112148 }
113149
150+ private getPendingTasks ( ) : PendingMacrotask [ ] {
151+ if ( ! this . taskTrackingZone ) {
152+ return [ ] ;
153+ }
154+
155+ return this . taskTrackingZone . macroTasks . map ( ( t : Task ) => {
156+ return {
157+ source : t . source ,
158+ isPeriodic : t . data . isPeriodic ,
159+ delay : t . data . delay ,
160+ // From TaskTrackingZone:
161+ // https://github.com/angular/zone.js/blob/master/lib/zone-spec/task-tracking.ts#L40
162+ creationLocation : ( t as any ) . creationLocation as Error ,
163+ // Added by Zones for XHRs
164+ // https://github.com/angular/zone.js/blob/master/lib/browser/browser.ts#L133
165+ xhr : ( t . data as any ) . target
166+ } ;
167+ } ) ;
168+ }
169+
170+ private addCallback ( cb : DoneCallback , timeout ?: number , updateCb ?: UpdateCallback ) {
171+ let timeoutId : any = - 1 ;
172+ if ( timeout && timeout > 0 ) {
173+ timeoutId = setTimeout ( ( ) => {
174+ this . _callbacks = this . _callbacks . filter ( ( cb ) => cb . timeoutId !== timeoutId ) ;
175+ cb ( this . _didWork , this . getPendingTasks ( ) ) ;
176+ } , timeout ) ;
177+ }
178+ this . _callbacks . push ( < WaitCallback > { doneCb : cb , timeoutId : timeoutId , updateCb : updateCb } ) ;
179+ }
180+
114181 /**
115- * Run callback when the application is stable
116- * @param callback function to be called after the application is stable
182+ * Wait for the application to be stable with a timeout. If the timeout is reached before that
183+ * happens, the callback receives a list of the macro tasks that were pending, otherwise null.
184+ *
185+ * @param doneCb The callback to invoke when Angular is stable or the timeout expires
186+ * whichever comes first.
187+ * @param timeout Optional. The maximum time to wait for Angular to become stable. If not
188+ * specified, whenStable() will wait forever.
189+ * @param updateCb Optional. If specified, this callback will be invoked whenever the set of
190+ * pending macrotasks changes. If this callback returns true doneCb will not be invoked
191+ * and no further updates will be issued.
117192 */
118- whenStable ( callback : Function ) : void {
119- this . _callbacks . push ( callback ) ;
193+ whenStable ( doneCb : Function , timeout ?: number , updateCb ?: Function ) : void {
194+ if ( updateCb && ! this . taskTrackingZone ) {
195+ throw new Error (
196+ 'Task tracking zone is required when passing an update callback to ' +
197+ 'whenStable(). Is "zone.js/dist/task-tracking.js" loaded?' ) ;
198+ }
199+ // These arguments are 'Function' above to keep the public API simple.
200+ this . addCallback ( doneCb as DoneCallback , timeout , updateCb as UpdateCallback ) ;
120201 this . _runCallbacksIfReady ( ) ;
121202 }
122203
123204 /**
124205 * Get the number of pending requests
206+ * @deprecated pending requests are now tracked with zones
125207 */
126208 getPendingRequestCount ( ) : number { return this . _pendingCount ; }
127209
0 commit comments