@@ -35,6 +35,17 @@ class WrappedTimer implements Timer {
3535 bool get isActive => _timer.isActive;
3636}
3737
38+ /**
39+ * Stores error information; delivered via [NgZone.onError] stream.
40+ */
41+ class NgZoneError {
42+ /// Error object thrown.
43+ final error;
44+ /// Either long or short chain of stack traces.
45+ final List stackTrace;
46+ NgZoneError (this .error, this .stackTrace);
47+ }
48+
3849/**
3950 * A `Zone` wrapper that lets you schedule tasks after its private microtask queue is exhausted but
4051 * before the next "VM turn", i.e. event loop iteration.
@@ -56,6 +67,12 @@ class NgZone {
5667 ZeroArgFunction _onEventDone;
5768 ErrorHandlingFn _onErrorHandler;
5869
70+ final _onTurnStartCtrl = new StreamController .broadcast (sync : true );
71+ final _onTurnDoneCtrl = new StreamController .broadcast (sync : true );
72+ final _onEventDoneCtrl = new StreamController .broadcast (sync : true );
73+ final _onErrorCtrl =
74+ new StreamController <NgZoneError >.broadcast (sync : true );
75+
5976 // Code executed in _mountZone does not trigger the onTurnDone.
6077 Zone _mountZone;
6178 // _innerZone is the child of _mountZone. Any code executed in this zone will trigger the
@@ -103,18 +120,43 @@ class NgZone {
103120 * Sets the zone hook that is called just before Angular event turn starts.
104121 * It is called once per browser event.
105122 */
123+ @Deprecated ('Use onTurnStart Stream instead' )
106124 void overrideOnTurnStart (ZeroArgFunction onTurnStartFn) {
107125 _onTurnStart = onTurnStartFn;
108126 }
109127
128+ void _notifyOnTurnStart () {
129+ this ._onTurnStartCtrl.add (null );
130+ }
131+
132+ /**
133+ * Notifies subscribers just before Angular event turn starts.
134+ *
135+ * Emits an event once per browser task that is handled by Angular.
136+ */
137+ Stream get onTurnStart => _onTurnStartCtrl.stream;
138+
110139 /**
111140 * Sets the zone hook that is called immediately after Angular processes
112141 * all pending microtasks.
113142 */
143+ @Deprecated ('Use onTurnDone Stream instead' )
114144 void overrideOnTurnDone (ZeroArgFunction onTurnDoneFn) {
115145 _onTurnDone = onTurnDoneFn;
116146 }
117147
148+ /**
149+ * Notifies subscribers immediately after the Angular zone is done processing
150+ * the current turn and any microtasks scheduled from that turn.
151+ *
152+ * Used by Angular as a signal to kick off change-detection.
153+ */
154+ Stream get onTurnDone => _onTurnDoneCtrl.stream;
155+
156+ void _notifyOnTurnDone () {
157+ this ._onTurnDoneCtrl.add (null );
158+ }
159+
118160 /**
119161 * Sets the zone hook that is called immediately after the last turn in
120162 * an event completes. At this point Angular will no longer attempt to
@@ -123,6 +165,7 @@ class NgZone {
123165 *
124166 * This hook is useful for validating application state (e.g. in a test).
125167 */
168+ @Deprecated ('Use onEventDone Stream instead' )
126169 void overrideOnEventDone (ZeroArgFunction onEventDoneFn,
127170 [bool waitForAsync = false ]) {
128171 _onEventDone = onEventDoneFn;
@@ -136,15 +179,55 @@ class NgZone {
136179 }
137180 }
138181
182+ /**
183+ * Notifies subscribers immediately after the final `onTurnDone` callback
184+ * before ending VM event.
185+ *
186+ * This event is useful for validating application state (e.g. in a test).
187+ */
188+ Stream get onEventDone => _onEventDoneCtrl.stream;
189+
190+ void _notifyOnEventDone () {
191+ this ._onEventDoneCtrl.add (null );
192+ }
193+
194+ /**
195+ * Whether there are any outstanding microtasks.
196+ */
197+ bool get hasPendingMicrotasks => _pendingMicrotasks > 0 ;
198+
199+ /**
200+ * Whether there are any outstanding timers.
201+ */
202+ bool get hasPendingTimers => _pendingTimers.isNotEmpty;
203+
204+ /**
205+ * Whether there are any outstanding asychnronous tasks of any kind that are
206+ * scheduled to run within Angular zone.
207+ *
208+ * Useful as a signal of UI stability. For example, when a test reaches a
209+ * point when [hasPendingAsyncTasks] is `false` it might be a good time to run
210+ * test expectations.
211+ */
212+ bool get hasPendingAsyncTasks => hasPendingMicrotasks || hasPendingTimers;
213+
139214 /**
140215 * Sets the zone hook that is called when an error is uncaught in the
141216 * Angular zone. The first argument is the error. The second argument is
142217 * the stack trace.
143218 */
219+ @Deprecated ('Use onError Stream instead' )
144220 void overrideOnErrorHandler (ErrorHandlingFn errorHandlingFn) {
145221 _onErrorHandler = errorHandlingFn;
146222 }
147223
224+ /**
225+ * Notifies subscribers whenever an error happens within the zone.
226+ *
227+ * Useful for logging.
228+ */
229+ Stream get onError => _onErrorCtrl.stream;
230+
148231 /**
149232 * Runs `fn` in the inner zone and returns whatever it returns.
150233 *
@@ -194,6 +277,7 @@ class NgZone {
194277 void _maybeStartVmTurn (ZoneDelegate parent) {
195278 if (! _hasExecutedCodeInInnerZone) {
196279 _hasExecutedCodeInInnerZone = true ;
280+ parent.run (_innerZone, _notifyOnTurnStart);
197281 if (_onTurnStart != null ) {
198282 parent.run (_innerZone, _onTurnStart);
199283 }
@@ -209,19 +293,25 @@ class NgZone {
209293 _nestedRun-- ;
210294 // If there are no more pending microtasks and we are not in a recursive call, this is the end of a turn
211295 if (_pendingMicrotasks == 0 && _nestedRun == 0 && ! _inVmTurnDone) {
212- if (_onTurnDone != null && _hasExecutedCodeInInnerZone) {
296+ if (_hasExecutedCodeInInnerZone) {
213297 // Trigger onTurnDone at the end of a turn if _innerZone has executed some code
214298 try {
215299 _inVmTurnDone = true ;
216- parent.run (_innerZone, _onTurnDone);
300+ _notifyOnTurnDone ();
301+ if (_onTurnDone != null ) {
302+ parent.run (_innerZone, _onTurnDone);
303+ }
217304 } finally {
218305 _inVmTurnDone = false ;
219306 _hasExecutedCodeInInnerZone = false ;
220307 }
221308 }
222309
223- if (_pendingMicrotasks == 0 && _onEventDone != null ) {
224- runOutsideAngular (_onEventDone);
310+ if (_pendingMicrotasks == 0 ) {
311+ _notifyOnEventDone ();
312+ if (_onEventDone != null ) {
313+ runOutsideAngular (_onEventDone);
314+ }
225315 }
226316 }
227317 }
@@ -248,18 +338,28 @@ class NgZone {
248338
249339 // Called by Chain.capture() on errors when long stack traces are enabled
250340 void _onErrorWithLongStackTrace (error, Chain chain) {
251- if (_onErrorHandler != null ) {
341+ if (_onErrorHandler != null || _onErrorCtrl.hasListener ) {
252342 final traces = chain.terse.traces.map ((t) => t.toString ()).toList ();
253- _onErrorHandler (error, traces);
343+ if (_onErrorCtrl.hasListener) {
344+ _onErrorCtrl.add (new NgZoneError (error, traces));
345+ }
346+ if (_onErrorHandler != null ) {
347+ _onErrorHandler (error, traces);
348+ }
254349 } else {
255350 throw error;
256351 }
257352 }
258353
259354 // Outer zone handleUnchaughtError when long stack traces are not used
260355 void _onErrorWithoutLongStackTrace (error, StackTrace trace) {
261- if (_onErrorHandler != null ) {
262- _onErrorHandler (error, [trace.toString ()]);
356+ if (_onErrorHandler != null || _onErrorCtrl.hasListener) {
357+ if (_onErrorHandler != null ) {
358+ _onErrorHandler (error, [trace.toString ()]);
359+ }
360+ if (_onErrorCtrl.hasListener) {
361+ _onErrorCtrl.add (new NgZoneError (error, [trace.toString ()]));
362+ }
263363 } else {
264364 throw error;
265365 }
0 commit comments