Navigation
CATEGORY: Angular

AngularJS: Count the number of watchers on a page

One of the main benefits AngularJS provides is its two way databinding. It uses dirty checking to implement this, so whenever any value changes, then all the databound values are checked to see if they have also changed and updated accordingly. Fortunately the dirty checking is usually fast and it can be performed without any perceived lag from the end user (see this Stack Overflow post for a reference).

However, it’s still good to pay attention that a page does not contain thousands upon thousands of databound elements, though, because the user can start noticing a lag, especially from mobile devices. There is a Chrome extension called Batarang that can show you how many databound elements a page has, but it hasn’t been updated in over a year, and its recent reviews complain that the extension is broken for them. So I created a simple script that counts the number of databound elements on the page.

AngularJS keeps track of two way databound elements by adding a watcher function to the $$watchers array on the scope. So to find out how many dirty checks AngularJS performs on the page, we just need to count the number of watchers on every scope.

To do this, the first thing we need to do is get a reference to all the scopes on the page. This can be done by taking advantage of the fact that AngularJS adds the ng-scope class to every element that is associated with a scope. And then we can call angular.element(element).scope() on each element to retrieve a reference to the actual scope itself. Here is a code example showing how to get the scope from a single element on the page.

var elementsWithScope = document.querySelectorAll('.ng-scope');
var firstElementWithScope = elementsWithScope[0];
var scope = angular.element(firstElementWithScope).scope();

And then we can loop through each scope and count the number of watchers.

Here is what the final code snippet looks like:

countWatchers = function() {
  var watchers, elementsWithScope, scope, i, len;
  watchers = 0;
  elementsWithScope = document.querySelectorAll('.ng-scope');
  for (i = 0, len = elementsWithScope.length; i < len; i++) {
    scope = angular.element(elementsWithScope[i]).scope();
    if (scope.$$watchers != null) {
      watchers += scope.$$watchers.length; 
    } 
  } 
  return watchers;
};

And once you include this JavaScript in you AngularJS site, you can open up the browser console on any page and type countWatchers(), and it will return the number of watchers on the page.

Bookmarklet

When I shared this code snippet with my co-worker, Eric Guthmann, he mentioned that it could easily be made into a bookmarklet so that it can be used on any AngularJS site. And then there wouldn’t be the need to manually include a JavaScript file with the website itself. So with his help, I created the bookmarklet below.

You can save the bookmarklet below by dragging and dropping it on your bookmark bar.

AngularJS Watcher Count

Test an AngularJS Directive with Protractor

When you write an awesome AngularJS directive, you want to share it with the world. So you put it in its own repository on Github and now everyone else can use it. And because others are using your directive, you want to make sure it’s well tested. Fortunately that’s easy to do with Karma, but what if Karma is not enough to run all the test scenarios? What if you need to use Protractor to test some UI interactions with your directive?

It’s not difficult to use Protractor for your UI tests on a full fledged application, but now that you have separated out and isolated a single directive in its own repository, you no longer have a full fledged application to test against. You only have a single directive. You don’t even have a simple html page to display the directive in.

I ran into this same issue myself a few weeks ago, and after a lot of trial and error I was finally able to figure out how to run Protractor tests against a standalone directive that’s in its own repository. So I’ll share my findings to make it easier for others dealing with the same situation.

Create the smallest AngularJS website

Before you create your first Protractor test, you first need to get your directive to run within a website. So create the smallest website possible with the least amount of JavaScript code needed to run your directive. That typically means creating a single html file that references AngularJS and your directive along with including the html element that initializes the directive.

Here’s an example directive to show you what I mean. This directive just toggles a class on an element when it is clicked.

angular.module('click-toggle', []).directive(
'clickToggleClass', function() {
  return function(scope, elem, attrs) {
    var classToAppend = attrs['clickToggleClass'];
    var classAppended = false;
    elem.on('click', function() {
      classAppended = !classAppended;
      elem.toggleClass(classToAppend, classAppended);
    });
  });
});

And here’s the html file that initializes an AngularJS app and uses the directive.

<html>
<head>
  <script src='angular.js'></script>
  <script src='click-toggle-class.js'><script>
</head>
<body ng-app='click-toggle'>
  <div class='alert' click-toggle-class='alert-info'>Click Me to Toggle the class</div>
</body>
<html>

Serve up the website using ExpressJS

Now we need to serve up the html page through a website. It’s not enough to just open the html directly from the filesystem because Protractor won’t work.

There’s plenty of web frameworks to choose from to serve up an html page through a website, but most of them are overkill because we only have a single html file and a couple JavaScript files that need to be served. So let’s use a minimalist approach and have ExpressJS serve up these files.

ExpressJS is a micro web application framework for NodeJS, and it makes it super simple to serve up files on your computer inside a website. For example, let’s say you have the following directory structure, and you want to serve up the index.html file at http://localhost:3000/index.html

app.js
/public
  index.html
  click-toggle-class.js
  angular.js

The app.js file is the configuration file for the ExpressJS web server. And since we’re only serving up static files, we only need 4 lines of code in our app.js file

var express = require('express');
var app = express();
app.use('/', express.static(__dirname + '/public'));
app.listen(3000);

Now install ExpressJS and startup the server with the following command:

# install expressjs
npm install express

# run the expressjs server
node app.js

And you can now view the html page from your browser at http://localhost:3000/index.html

Run the Protractor tests

And finally we’ll create our protractor tests and run it against the directive.

// click-toggle-class_spec.js

describe('click-toggle-class', function() {
  it('adds the class on click when it did not previously have the class', function() {
    browser.get('');
    var el = element(By.css('div'));
    el.click()
    expect(el.getAttribute('class')).toBe('highlight');
  });
  it('removes the class on click when it previously had the class', function() {
    browser.get('');
    var el = element(By.css('div'));

    // first add the class by performing a click
    el.click()
    expect(el.getAttribute('class')).toBe('highlight');

    // now click again and confirm the class has been removed
    el.click()
    expect(el.getAttribute('class')).toBe('');
  });
});

Our Protractor config file will look like this:

// protractor.conf.js

exports.config = {
  specs: [ 'click-toggle-class_spec.js' ],
  capability: { browserName: 'chrome' },
  baseUrl: 'http://localhost:3000/index.html'
};

And we can now run our Protractor tests with the following command:

protractor protractor.conf.js

Conclusion

As you can see, it’s not completely straightforward to run Protractor tests against a standalone directive. But with the help of ExpressJS and creating a minimal website to run the directive, it is possible.

Add Comma Separated Thousands to Number Inputs in AngularJS

AngularJS provides the number and currency filters to automatically format numbers with comma thousands separators. But these filters don’t work when you want to format a number within an input text box.

Ideally you will want to show the comma thousands separators to start with, and remove them when the input box gains focus. I couldn’t find any directive that already did this, so I created the fcsa-number directive to provide this functionality. I also added a few validations that can be applied to it as well.

Here’s a few examples of what the directive does and the validations it provides:

If you want to use it on your site, just follow the installation instructions in the Readme file on the project’s Github site.

Validate and Format Numbers in AngularJS

When I want to provide a number input box with basic validation and formatting in an AngularJS app, I have found the default way to be lacking. The default way to is to declare an input element with a number type: <input type="number" />. However, I want to automatically add commas as thousands separators, and I want to validate no more than 2 decimals are entered for currency fields. Both of these are not possible with the the default way.

So I created the FCSA Number directive that provides this functionality. (view on Github)

Here’s a quick demo of the available validations and formatting provided by the directive.

To use it on your site

Install it on your site either with bower: bower install angular-fcsa-number

Or install it manually by copying src/fcsaNumber.js from the Github project into your project.

And make sure to reference the src/fcsaNumber.js in your JavaScript files.

Then add the fcsa-number module as a dependency to you Angular app.

angular.module('yourApp', ['fcsa-number']);

And finally add the fcsa-number attribute to textboxes you want to have validated and formatted with thousands separators.

<input type='text' ng-model='company.employeeCount' fcsa-number />

When an invalid number is entered by the user, the form and control will become invalid and the fcsa-number-invalid class will be added to the text box.

Run Protractor Tests in Parallel

My team has been using Protractor for our end to end tests for a couple months now, and the amount of time the test suite takes to finish has grown as we add more tests. So we started looking for ways to speed our tests up. We already have a Selenium grid running with a few browser instances connected to it. Ideally we would like to split the tests out to be ran in parallel among the browser instances in the grid.

The first configuration setting we tried was setting the count within the capabilities section of the configuration file.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    count: 2
  },

  /* ... more protractor config values ... */
}

We ran Protractor and noticed both browser instances ran the tests. But after closer examination, we realized each browser instance was running all the tests. So the tests were being ran in parallel, but it wasn’t saving us any time because each test was ran twice.

I then dug into the source code for protractor in Github and found an example configuration file with comments about each setting. The comment for the count setting confirmed what we were seeing: ‘Number of times to run this set of capabilities (in parallel, unless limited by maxSessions)’. I continued reading the example configuration file and found what I was looking for right below the count setting.

The comment for shardTestFiles reads: ‘If this is set to be true, specs will be sharded by file (i.e. all files to be run by this set of capabilities will run in parallel). Default is false.’

So then I updated my config to the following and ran Protractor again.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    shardTestFiles: true
  },

  /* ... more protractor config values ... */
}

However, this time only one browser instance ran the tests. They were not sharded between the 2 browser instances connected to the Selenium grid.

I went back to the example config file and then found the maxInstances setting, and read its comment that said this is only needed if shardTestFiles is true. So I added maxInstances to the config and ran Protractor again.

exports.config = {
  capabilities: {
    browserName: 'chrome',
    shardTestFiles: true,
    maxInstances: 2
  },

  /* ... more protractor config values ... */
}

And this time the test files were successfully sharded between the 2 browser instances.

Our tests were taking around 3 minutes to complete when they weren’t split up, and then they took around 2 minutes to complete after we split them among 2 browsers. So we weren’t able to realize a 50% reduction in the overall test time by splitting the tests between two browsers, but our overall test time was significantly improved.

PS

The shardTestFiles configuration was introduced in version 0.24. So if you’re running an earlier version then your test files will not be sharded.

There are no more results.