Speeding up Angular in Production

When using certain directives, Angular attaches additional (unnecessary) debug information to the elements they are applied to. When we use interpolation or data-binding directives to evaluate an expression, Angular adds an additional ng-binding class to the directive’s, or directive’s parent element. Say we have a scope property like this:

app.controller('AppController', ['$scope', function ($scope) {
  $scope.name = 'Troy';
}]);

And we have an expression in our HTML code like this:

<p>Hello {{name}}!</p>

We get this compiled code:

<p class="ng-binding">Hello Troy!</p>

The same happens when we use the ng-bind or ng-bind-html directives.

ng-bind is pretty similar to the interpolation directive, but prevent flashes of uncompiled content from flickering. ng-bind-html lets us evaluate expressions that have HTML code as value while the HTML itself is interpreted by the browser.

To make things a bit more clear, here’s our example as ng-bind version:

<p>Hello <span ng-bind="name"></span>!</p>

Which would end up in a DOM that looks like this:

<p>Hello <span class="ng-binding" ng-bind="name">Troy</span>!</p>

Angular has some more cases where additional debug information is attached to an element. When Angular’s compiler creates a new scope, it adds either ng-scope or ng-isolated-scope classes to the element, depending on what kind of scope is created.

For example, a directive like this:

app.directive('myComponent', function () {
  return {
    scope: {},
    template: 'This is a component with isolated scope.'
  };
});

Which is used like this:

<my-component></my-component>

Creates this compiled DOM:

<my-component class="ng-isolated-scope">
  This is a component with isolated scope.
</my-component>

The compiler adds an ng-isolated-scope class to that element, because it has an isolated scope. But that’s not all. What also happens is, that the actual scope is added to the DOM element as well. Wait… the scope object itself? Hey that means we can access it imperatively in JavaScript, right? Yes!

Depending on what type of scope is created, the corresponding element gets either a .scope() or .isolateScope() method, that we can call to access the scope object for debugging purposes. Just notice that with element we refer to angular.element().

Running the following code in a browser console would display the components scope object:

angular
  .element(document.querySelector('my-component'))
  .isolateScope();

Why these classes and element properties are added by the compiler? Turns out that tools like Protractor and the Angular Batarang need all this information to actually run. Batarang for example displays scope data in the developer tools.

Having Angular providing all this information in our application is super useful when it comes to debugging. Tools like Protractor and Batarang rely on that data and (are supposed to) make debugging eaiser. Batarang always locks up on me so I just console.logeverything in development. These additional properties and classes that are added to the DOM come with a significant performance cost (depending on how much is stored on the scope). Plus, most DOM operations are expensive anyway.

Ok, got that. How do we actually disable debug info for production?

In order to turn off this default behavior, all we have to do is to call the .debugInfoEnabled() method on the $compileProvider during our application’s configuration phase (since this is the only place where we have access to providers).

app.config(['$compileProvider', function ($compileProvider) {
  // disable debug info
  $compileProvider.debugInfoEnabled(false);
}]);

Just one line of code and our production application runs like a dream!

But what if we do want to have this debug information in our application because something’s wrong in our production environment and we need to debug? Angular has us covered. The global angular object comes with a new .reloadWithDebugInfo() method.

It reloads the browser with debug information to make your life easier again. And since the angular object is global, we can just call it directly from the browsers console. Neat ha?

Important Note: When you disable debugInfo, you will no longer be able to use $element.scope() or $element.isolateScope().

Show Comments