DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Zone-Free Angular: Unlocking High-Performance Change Detection With Signals and Modern Reactivity
  • When Angular APIs Return 200 but the Frontend Is Already Failing Users
  • Why Angular Performance Problems Are Often Backend Problems
  • Faster Releases With DevOps: Java Microservices and Angular UI in CI/CD

Trending

  • Why We Chose Iceberg Over Delta After Evaluating Both at Scale
  • How to Build and Optimize AI Models for Real-World Applications
  • Comparing Top Gen AI Frameworks for Java in 2026
  • The ORM Is Over: AI-Written SQL Is the New Data Access Layer
  1. DZone
  2. Coding
  3. Frameworks
  4. Understanding NgZone

Understanding NgZone

Angular 2 does a lot of things differently from Angular 1, and one of its greatest changes is in change detection.

By 
Tiago Roldao user avatar
Tiago Roldao
·
Oct. 07, 16 · Opinion
Likes (9)
Comment
Save
Tweet
Share
57.2K Views

Join the DZone community and get the full member experience.

Join For Free

Angular 2 does a lot of things differently from Angular 1, and one of its greatest changes is in change detection. understanding how it works has been essential, particularly when using Protractor for E2E testing. This explores how to work with zones for testing and performance. A live example of the mentioned code is here.

The biggest change in how Angular 2 handles change detection, as far as a user is concerned, is that it now happens transparently through zone.js.

This is very different from Angular 1, where you have to specifically tell it to synchronize – even though both the built-in services and the template bindings do this internally. What this means is, while $http or $timeout do trigger change detection, if you use a third party script, your Angular 1 app won’t know anything happened until you call $apply().

Angular 2, on the other hand, does this entirely implicitly –  all code run within the app’s Components, Services or Pipes exists inside that app’s zone, and just works.

So, What Is a Zone?

zone.js’s zones are actually a pretty complicated concept to get our head around. Running the risk of over-simplifying, they can be described simply as managed code calling contexts – closed environments that let you monitor, control, and react to all events, from asynchronous tasks to errors thrown.

The reason this works is, inside these zones, zone.js overrides and augments the native methods – Promises, timeouts, and so on, meaning your code doesn’t need to know about zone to be monitored by it. Everytime you call setTimeout, for instance, you unwillingly call an augmented version, which zone uses to keep tabs on things. 

What Angular does is use zone.js to create it’s own zone and listen to it, and what this means for us as angular users is this – all code run inside an angularapp is automatically listened to, with no work on our part.

Most times, this works just fine – all change detection “just works” and Protractor will wait for any asynchronous code you might have. But what if you don’t want it to? There are a few cases where you might want to tell angular not to wait for / listen to some tasks:

  • An interval to loop an animation
  • A long-polling http request / socket to receive regular updates from a backend
  • A header component that listens to changes in the Router and updates accordingly

These are cases where you don’t want angular to wait on asynchronous tasks/ change detection to run every time they run.

Control Where Your Code Runs With NgZone

NgZone gives you back control of your code’s execution. There are two relevant methods in NgZone – run and runOutsideAngular:

  • runOutsideAngular runs a given function outside the angular zone, meaning its code won’t trigger change detection.
  • run runs a given function inside the angular zone. It is meant to be run inside a block created by runOutsideAngular, to jump back in, and tell angular to start listening again.

So, this code will have problems being tested, as the app will be constantly unstable:

this._sub = Observable.timer(1000, 1000)
    .subscribe(i => {
        this.content = "Loaded! " + i;
    });

Using NgZone, we can avoid that problem:

this.ngZone.runOutsideAngular(() => {
    this._sub = Observable.timer(1000, 1000)
    .subscribe(i => this.ngZone.run(() => {
        this.content = "Loaded! " + i;
    }));
});

Simplifying usage

After understanding how NgZone works, we can simplify its usage, so that we don’t need to sprinkle NgZone.runOutsideAngular and NgZone.run all over the place.

We can create a NgSafeZone service to do exactly that, as the most common use case for this is:

  • Subscribe to an Observable outside the angular zone
  • Return to the angular zone when reacting to that Observable.
@Injectable()
export class SafeNgZone{

  constructor(private ngZone: NgZone) {
  }

  safeSubscribe(
    observable: Observable<T>, 
    observerOrNext?: PartialObserver<T> | ((value: T) => void), 
    error?: (error: any) => void, 
    complete?: () => void) {
      return this.ngZone.runOutsideAngular(() => 
        return observable.subscribe(
          this.callbackSubscriber(observerOrNext),
          error,
          complete));
  }

  private callbackSubscriber (obs: PartialObserver<T> | 
    ((value: T) => void)) {
    if (typeof obs === "object") {
      let observer: PartialObserver<T> = {
          next: (value: T) => {
            obs['next'] && 
              this.ngZone.run(() => obs['next'](value));
          },
          error: (err: any) => {
            obs['error'] && 
              this.ngZone.run(() => obs['error'](value));
          },
          complete: () => {
            obs['complete'] && 
              this.ngZone.run(() => obs['complete'](value));
          }
      };

      return observer;
    }
    else if (typeof obs === "function") {
      return (value: T) => {
        this.ngZone.run(() => obs(value));
      }
    }
  }
}

With this the previous code gets simplified quite a bit:

// The following:
this.ngZone.runOutsideAngular(() => {
    this._sub = Observable.timer(1000, 1000)
    .subscribe(i => this.ngZone.run(() => {
        this.content = "Loaded! " + i;
    }));
});

// Becomes:
this._sub = this.safeNgZone.safeSubscribe(
    Observable.timer(1000, 1000),
    i => this.content = "Loaded! " + i);
AngularJS

Published at DZone with permission of Tiago Roldao. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Zone-Free Angular: Unlocking High-Performance Change Detection With Signals and Modern Reactivity
  • When Angular APIs Return 200 but the Frontend Is Already Failing Users
  • Why Angular Performance Problems Are Often Backend Problems
  • Faster Releases With DevOps: Java Microservices and Angular UI in CI/CD

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook