UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

399 lines 16.1 kB
import { ListWrapper, StringMapWrapper } from 'angular2/src/facade/collection'; import { normalizeBlank, isPresent, global } from 'angular2/src/facade/lang'; import { ObservableWrapper, EventEmitter } from 'angular2/src/facade/async'; import { wtfLeave, wtfCreateScope } from '../profile/profile'; /** * Stores error information; delivered via [NgZone.onError] stream. */ export class NgZoneError { constructor(error, stackTrace) { this.error = error; this.stackTrace = stackTrace; } } /** * An injectable service for executing work inside or outside of the Angular zone. * * The most common use of this service is to optimize performance when starting a work consisting of * one or more asynchronous tasks that don't require UI updates or error handling to be handled by * Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks * can reenter the Angular zone via {@link #run}. * * <!-- TODO: add/fix links to: * - docs explaining zones and the use of zones in Angular and change-detection * - link to runOutsideAngular/run (throughout this file!) * --> * * ### Example ([live demo](http://plnkr.co/edit/lY9m8HLy7z06vDoUaSN2?p=preview)) * ``` * import {Component, View, NgIf, NgZone} from 'angular2/angular2'; * * @Component({ * selector: 'ng-zone-demo'. * template: ` * <h2>Demo: NgZone</h2> * * <p>Progress: {{progress}}%</p> * <p *ng-if="progress >= 100">Done processing {{label}} of Angular zone!</p> * * <button (click)="processWithinAngularZone()">Process within Angular zone</button> * <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button> * `, * directives: [NgIf] * }) * export class NgZoneDemo { * progress: number = 0; * label: string; * * constructor(private _ngZone: NgZone) {} * * // Loop inside the Angular zone * // so the UI DOES refresh after each setTimeout cycle * processWithinAngularZone() { * this.label = 'inside'; * this.progress = 0; * this._increaseProgress(() => console.log('Inside Done!')); * } * * // Loop outside of the Angular zone * // so the UI DOES NOT refresh after each setTimeout cycle * processOutsideOfAngularZone() { * this.label = 'outside'; * this.progress = 0; * this._ngZone.runOutsideAngular(() => { * this._increaseProgress(() => { * // reenter the Angular zone and display done * this._ngZone.run(() => {console.log('Outside Done!') }); * }})); * } * * * _increaseProgress(doneCallback: () => void) { * this.progress += 1; * console.log(`Current progress: ${this.progress}%`); * * if (this.progress < 100) { * window.setTimeout(() => this._increaseProgress(doneCallback)), 10) * } else { * doneCallback(); * } * } * } * ``` */ export class NgZone { /** * @param {bool} enableLongStackTrace whether to enable long stack trace. They should only be * enabled in development mode as they significantly impact perf. */ constructor({ enableLongStackTrace }) { /** @internal */ this._runScope = wtfCreateScope(`NgZone#run()`); /** @internal */ this._microtaskScope = wtfCreateScope(`NgZone#microtask()`); // Number of microtasks pending from _innerZone (& descendants) /** @internal */ this._pendingMicrotasks = 0; // Whether some code has been executed in the _innerZone (& descendants) in the current turn /** @internal */ this._hasExecutedCodeInInnerZone = false; // run() call depth in _mountZone. 0 at the end of a macrotask // zone.run(() => { // top-level call // zone.run(() => {}); // nested call -> in-turn // }); /** @internal */ this._nestedRun = 0; /** @internal */ this._inVmTurnDone = false; /** @internal */ this._pendingTimeouts = []; if (global.zone) { this._disabled = false; this._mountZone = global.zone; this._innerZone = this._createInnerZone(this._mountZone, enableLongStackTrace); } else { this._disabled = true; this._mountZone = null; } this._onTurnStartEvents = new EventEmitter(false); this._onTurnDoneEvents = new EventEmitter(false); this._onEventDoneEvents = new EventEmitter(false); this._onErrorEvents = new EventEmitter(false); } /** * Sets the zone hook that is called just before a browser task that is handled by Angular * executes. * * The hook is called once per browser task that is handled by Angular. * * Setting the hook overrides any previously set hook. * * @deprecated this API will be removed in the future. Use `onTurnStart` instead. */ overrideOnTurnStart(onTurnStartHook) { this._onTurnStart = normalizeBlank(onTurnStartHook); } /** * Notifies subscribers just before Angular event turn starts. * * Emits an event once per browser task that is handled by Angular. */ get onTurnStart() { return this._onTurnStartEvents; } /** @internal */ _notifyOnTurnStart(parentRun) { parentRun.call(this._innerZone, () => { this._onTurnStartEvents.next(null); }); } /** * Sets the zone hook that is called immediately after Angular zone is done processing the current * task and any microtasks scheduled from that task. * * This is where we typically do change-detection. * * The hook is called once per browser task that is handled by Angular. * * Setting the hook overrides any previously set hook. * * @deprecated this API will be removed in the future. Use `onTurnDone` instead. */ overrideOnTurnDone(onTurnDoneHook) { this._onTurnDone = normalizeBlank(onTurnDoneHook); } /** * Notifies subscribers immediately after Angular zone is done processing * the current turn and any microtasks scheduled from that turn. * * Used by Angular as a signal to kick off change-detection. */ get onTurnDone() { return this._onTurnDoneEvents; } /** @internal */ _notifyOnTurnDone(parentRun) { parentRun.call(this._innerZone, () => { this._onTurnDoneEvents.next(null); }); } /** * Sets the zone hook that is called immediately after the `onTurnDone` callback is called and any * microstasks scheduled from within that callback are drained. * * `onEventDoneFn` is executed outside Angular zone, which means that we will no longer attempt to * sync the UI with any model changes that occur within this callback. * * This hook is useful for validating application state (e.g. in a test). * * Setting the hook overrides any previously set hook. * * @deprecated this API will be removed in the future. Use `onEventDone` instead. */ overrideOnEventDone(onEventDoneFn, opt_waitForAsync = false) { var normalizedOnEventDone = normalizeBlank(onEventDoneFn); if (opt_waitForAsync) { this._onEventDone = () => { if (!this._pendingTimeouts.length) { normalizedOnEventDone(); } }; } else { this._onEventDone = normalizedOnEventDone; } } /** * Notifies subscribers immediately after the final `onTurnDone` callback * before ending VM event. * * This event is useful for validating application state (e.g. in a test). */ get onEventDone() { return this._onEventDoneEvents; } /** @internal */ _notifyOnEventDone() { this.runOutsideAngular(() => { this._onEventDoneEvents.next(null); }); } /** * Whether there are any outstanding microtasks. */ get hasPendingMicrotasks() { return this._pendingMicrotasks > 0; } /** * Whether there are any outstanding timers. */ get hasPendingTimers() { return this._pendingTimeouts.length > 0; } /** * Whether there are any outstanding asychnronous tasks of any kind that are * scheduled to run within Angular zone. * * Useful as a signal of UI stability. For example, when a test reaches a * point when [hasPendingAsyncTasks] is `false` it might be a good time to run * test expectations. */ get hasPendingAsyncTasks() { return this.hasPendingMicrotasks || this.hasPendingTimers; } /** * Sets the zone hook that is called when an error is thrown in the Angular zone. * * Setting the hook overrides any previously set hook. * * @deprecated this API will be removed in the future. Use `onError` instead. */ overrideOnErrorHandler(errorHandler) { this._onErrorHandler = normalizeBlank(errorHandler); } get onError() { return this._onErrorEvents; } /** * Executes the `fn` function synchronously within the Angular zone and returns value returned by * the function. * * Running functions via `run` allows you to reenter Angular zone from a task that was executed * outside of the Angular zone (typically started via {@link #runOutsideAngular}). * * Any future tasks or microtasks scheduled from within this function will continue executing from * within the Angular zone. */ run(fn) { if (this._disabled) { return fn(); } else { var s = this._runScope(); try { return this._innerZone.run(fn); } finally { wtfLeave(s); } } } /** * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by * the function. * * Running functions via `runOutsideAngular` allows you to escape Angular's zone and do work that * doesn't trigger Angular change-detection or is subject to Angular's error handling. * * Any future tasks or microtasks scheduled from within this function will continue executing from * outside of the Angular zone. * * Use {@link #run} to reenter the Angular zone and do work that updates the application model. */ runOutsideAngular(fn) { if (this._disabled) { return fn(); } else { return this._mountZone.run(fn); } } /** @internal */ _createInnerZone(zone, enableLongStackTrace) { var microtaskScope = this._microtaskScope; var ngZone = this; var errorHandling; if (enableLongStackTrace) { errorHandling = StringMapWrapper.merge(Zone.longStackTraceZone, { onError: function (e) { ngZone._notifyOnError(this, e); } }); } else { errorHandling = { onError: function (e) { ngZone._notifyOnError(this, e); } }; } return zone.fork(errorHandling) .fork({ '$run': function (parentRun) { return function () { try { ngZone._nestedRun++; if (!ngZone._hasExecutedCodeInInnerZone) { ngZone._hasExecutedCodeInInnerZone = true; ngZone._notifyOnTurnStart(parentRun); if (ngZone._onTurnStart) { parentRun.call(ngZone._innerZone, ngZone._onTurnStart); } } return parentRun.apply(this, arguments); } finally { ngZone._nestedRun--; // If there are no more pending microtasks, we are at the end of a VM turn (or in // onTurnStart) // _nestedRun will be 0 at the end of a macrotasks (it could be > 0 when there are // nested calls // to run()). if (ngZone._pendingMicrotasks == 0 && ngZone._nestedRun == 0 && !this._inVmTurnDone) { if (ngZone._hasExecutedCodeInInnerZone) { try { this._inVmTurnDone = true; ngZone._notifyOnTurnDone(parentRun); if (ngZone._onTurnDone) { parentRun.call(ngZone._innerZone, ngZone._onTurnDone); } } finally { this._inVmTurnDone = false; ngZone._hasExecutedCodeInInnerZone = false; } } if (ngZone._pendingMicrotasks === 0) { ngZone._notifyOnEventDone(); if (isPresent(ngZone._onEventDone)) { ngZone.runOutsideAngular(ngZone._onEventDone); } } } } }; }, '$scheduleMicrotask': function (parentScheduleMicrotask) { return function (fn) { ngZone._pendingMicrotasks++; var microtask = function () { var s = microtaskScope(); try { fn(); } finally { ngZone._pendingMicrotasks--; wtfLeave(s); } }; parentScheduleMicrotask.call(this, microtask); }; }, '$setTimeout': function (parentSetTimeout) { return function (fn, delay, ...args) { var id; var cb = function () { fn(); ListWrapper.remove(ngZone._pendingTimeouts, id); }; id = parentSetTimeout(cb, delay, args); ngZone._pendingTimeouts.push(id); return id; }; }, '$clearTimeout': function (parentClearTimeout) { return function (id) { parentClearTimeout(id); ListWrapper.remove(ngZone._pendingTimeouts, id); }; }, _innerZone: true }); } /** @internal */ _notifyOnError(zone, e) { if (isPresent(this._onErrorHandler) || ObservableWrapper.hasSubscribers(this._onErrorEvents)) { var trace = [normalizeBlank(e.stack)]; while (zone && zone.constructedAtException) { trace.push(zone.constructedAtException.get()); zone = zone.parent; } if (ObservableWrapper.hasSubscribers(this._onErrorEvents)) { ObservableWrapper.callNext(this._onErrorEvents, new NgZoneError(e, trace)); } if (isPresent(this._onErrorHandler)) { this._onErrorHandler(e, trace); } } else { console.log('## _notifyOnError ##'); console.log(e.stack); throw e; } } } //# sourceMappingURL=ng_zone.js.map