Skip to content
This repository has been archived by the owner on May 3, 2019. It is now read-only.

Commit

Permalink
improving demo, adding function callbacks on directive, and bumping v…
Browse files Browse the repository at this point in the history
…ersions
  • Loading branch information
Kent C. Dodds committed Aug 18, 2014
1 parent 4836a70 commit 35c8b99
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 25 deletions.
113 changes: 102 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ng-stats

Little utility to show stats about your page's angular digest/watches.
Little utility to show stats about your page's angular digest/watches. This library currently has a simple script to
produce a chart (see below). It also creates a module called `angularStats` which has a directive called `angular-stats`
which can be used to put angular stats on a specific place on the page that you specify.

Example Green (digests are running smoothly):

Expand All @@ -10,42 +12,131 @@ Example Red (digests are taking a bit...):

![Example Red](http://cl.ly/image/2f3L1B3b1q2V/ng-stats-bad.png)

The first number is the number of watchers on the page (including `{{variables}}`, `$scope.$watch`, etc.). The second number is how long (in milliseconds) it takes angular to go through each digest cycle on average (bigger is worse). The graph shows a trend of the digest cycle average time.
[Interactive Demo](http://kent.doddsfamily.us/ng-stats)

## Install and use
The first number is the number of watchers on the page (including `{{variables}}`, `$scope.$watch`, etc.). The second
number is how long (in milliseconds) it takes angular to go through each digest cycle on average (bigger is worse). The
graph shows a trend of the digest cycle average time.

This actually is easiest to work with best as a [Chrome DevTools Snippet](https://developer.chrome.com/devtools/docs/authoring-development-workflow#snippets). Just copy/paste the `ng-stats.js` file into a snippet.
## Installation

If you just want the chart for development purposes, it's actually easiest to use as a
[Chrome DevTools Snippet](https://developer.chrome.com/devtools/docs/authoring-development-workflow#snippets).
Just copy/paste the `ng-stats.js` file into a snippet.

However, it uses UMD, so you can also include it in your app if you want via:

`$ npm|bower install ng-stats`

or download `ng-stats.js` and

`<script src="path-to-ng-stats"></script>`
`<script src="path-to/ng-stats.js"></script>`

or

```javascript
var showAngularStats = require('path-to-ng-stats'); // if using commonjs or amd otherwise it's global with that name
showAngularStats(options);
var showAngularStats = require('path-to-ng-stats');
```

## Options
You now have a `angularStats` module and `showAngularStats` function you can call

## Chart

### Usage

Simply invoke `showAngularStats( { options } )` and the chart will appear.

### Options

You can pass the function one (optional) argument. If you pass `false` it will turn off "autoload" and do nothing. You can also pass an object with other options:

### position (object) - default: `'topleft'`
#### position (object) - default: `'topleft'`

Controls the position of the graphic.
Possible values: Any combination of `top`, `left`, `right`, `bottom`.

### digestTimeThreshold (number) - default: 16
#### digestTimeThreshold (number) - default: 16

The time (in milliseconds) where it goes from red to green.

### autoload (boolean) - default: false
#### autoload (boolean) - default: false

Uses sessionStorage to store whether the graphic should be automatically loaded every time the page is reloaded.

## Module

Simply declare it as a dependency `angular.module('your-mod', ['angularStats']);`

Then use the directive:

```
<div angular-stats watch-count=".watch-count" digest-length=".digest-length"
on-watch-count-update="onWatchCountUpdate(watchCount)"
on-digest-length-update="onDigestLengthUpdate(digestLength)">
Watch Count: <span class="watch-count"></span><br />
Digest Cycle Length: <span class="digest-length"></span>
</div>
```

### angular-stats attributes

#### angular-stats

The directive itself. No value is expected

#### watch-count

Having this attribute will keep track of the watch count and update the `text` of a specified element.
Possible values are:

1. Selector for a child element to update
2. no value - refers to the current element (updates the text of the current element)

#### watch-count-root

`angular-stats` defaults to keeping track of the watch count for the whole page, however if you want to keep track of a
specific element (and its children), provide this with a element query selector. As a convenience, if `this` is provided
then the `watch-count-root` will be set to the element itself. Also, if you want to scope the query selector to the
element, add `watch-count-of-child` as an attribute (no value)

#### on-watch-count-update

Because of the performance implications of calculating the watch count, this is not called every digest but a maximum
of once every 300ms. Still avoid invoking another digest here though. The name of the variable passed is `watchCount`
(like you see in the example).

#### digest-length

This works similar to the `watch-count` attribute. It's presence will cause the directive to keep track of the
`digest-length` and will update the `text` of a specified element (rounds to two decimal places). Possible values are:

1. Selector for a child element to update
2. no value - refers to the current element (updates the text of the current element)

#### on-digest-length-update

Pass an expression to evaluate with every digest length update. This gets called on every digest (so be sure you don't
invoke another digest in this handler or you'll get an infinite loop of doom). The name of the variable passed is
`digestLength` (as in the example).

## Roadmap

- Add analysis to highlight areas on the page that have highest watch counts.
- Somehow find out which watches are taking the longest... Ideas on implementation are welcome...
- See what could be done with the new scoped digest coming in Angular version 1.3.
- Count the number of digests or provide some analytics for frequency?
- Create a Chrome Extension for the chart or integrate with [batarang](https://github.com/angular/angularjs-batarang)?
- Other ideas?

## Other notes

### Performance impact

This will not impact the speed of your application at all until you actually use it. It also will hopefully only
negatively impact your app's performance minimally. This is intended to be used in development only for debugging
purposes so it shouldn't matter much anyway. It should be noted that calculating the watch count can be pretty
expensive, so it's throttled to be calculated a minimum of 300ms.

## License

MIT
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "ng-stats",
"main": "ng-stats.js",
"version": "1.0.1",
"version": "1.0.2",
"authors": [
"Kent C. Dodds <[email protected]> (http://kent.doddsfamily.us)",
"Viper Bailey <[email protected]> (http://jinxidoru.blogspot.com)"
Expand Down
47 changes: 44 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,21 @@ <h2>Chart</h2>
</div>
<div>
<h2>Directive</h2>
<div angular-stats watch-count=".watch-count" digest-length=".digest-length">
<div angular-stats watch-count=".watch-count" digest-length=".digest-length"
on-watch-count-update="vm.onWatchCountUpdate(watchCount)"
on-digest-length-update="vm.onDigestLengthUpdate(digestLength)">
Watch Count: <span class="watch-count"></span><br />
Digest Cycle Length: <span class="digest-length"></span>
</div>
</div>
<div>
<h2>Watcher & Digest Simulation</h2>
<label>Total Items (watch count): <input type="number" ng-model="vm.itemCount" max="5000" min="0" /></label><br />
<label>Filter Wait (digest length): <input type="number" ng-model="vm.filterWait.waitTime" max="50" min="0" /></label>
<div ng-repeat="item in vm.totalItems">
{{item | longFilter}}
</div>
</div>

<script src="angular.js"></script>
<script src="ng-stats.js"></script>
Expand All @@ -40,16 +50,36 @@ <h2>Directive</h2>
<br />
<label><input type="checkbox" ng-model="autoload" /> Autoload</label>
<br />
<button>Show Stats</button>
<button>Show Stats</button> (must click to update)<br />
<a ng-click="hideStats()" href="#">Hide Stats</a>
</div>
</script>
<script>
(function() {
'use strict';
var app = angular.module('ngStatsDemo', ['angularStats']);
app.controller('MainCtrl', function MainCtrl() {
app.value('filterWait', {
waitTime: 10
});
app.controller('MainCtrl', function MainCtrl($scope, filterWait) {
var vm = this;
vm.onWatchCountUpdate = function(count) {
console.log('onWatchCountUpdate', count);
};
vm.onDigestLengthUpdate = function(digestLength) {
console.log('onDigestLengthUpdate', digestLength);
};
vm.filterWait = filterWait;
vm.itemCount = 5;

$scope.$watch(function() {
return vm.itemCount;
}, function(newVal) {
vm.totalItems = [];
for (var i = 0; i < newVal; i++) {
vm.totalItems.push(i);
}
});
});

// using directive to prevent a digest
Expand Down Expand Up @@ -86,6 +116,17 @@ <h2>Directive</h2>
}
}
});

app.filter('longFilter', function(filterWait) {
'use strict';
return function longFilter(input) {
var now = new Date();
while(new Date() - now < filterWait.waitTime) {
// keep waiting...
}
return 'Item ' + input + ' waited ' + filterWait.waitTime + 'ms';
}
});
})();
</script>
<script>
Expand Down
33 changes: 24 additions & 9 deletions ng-stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,14 @@
'use strict';
hijackDigest();
return {
scope: true,
scope: {
digestLength: '@',
watchCount: '@',
watchCountRoot: '@',
onDigestLengthUpdate: '&?',
onWatchCountUpdate: '&?'
},
link: function(scope, el, attrs) {
scope.ngStats = {
watchCount: 0
};

if (attrs.hasOwnProperty('digestLength')) {
var digestEl = el;
Expand All @@ -197,12 +200,12 @@
if (attrs.hasOwnProperty('watchCount')) {
var watchCountRoot;
var watchCountEl = el;
if (attrs.watchCount) {
if (scope.watchCount) {
watchCountEl = angular.element(el[0].querySelector(attrs.watchCount));
}

if (attrs.watchCountRoot) {
if (attrs.watchCountRoot === 'this') {
if (scope.watchCountRoot) {
if (scope.watchCountRoot === 'this') {
watchCountRoot = el;
} else {
// In the case this directive is being compiled and it's not in the dom,
Expand All @@ -213,9 +216,9 @@
} else {
rootParent = findRootOfElement(el);
}
watchCountRoot = angular.element(rootParent.querySelector(attrs.watchCountRoot));
watchCountRoot = angular.element(rootParent.querySelector(scope.watchCountRoot));
if (!watchCountRoot.length) {
throw new Error('no element at selector: ' + attrs.watchCountRoot);
throw new Error('no element at selector: ' + scope.watchCountRoot);
}
}
}
Expand All @@ -228,6 +231,18 @@
watchCountEl.text(watchCount);
});
}

if (scope.onWatchCountUpdate) {
listeners.watchCount.push(function(count) {
scope.onWatchCountUpdate({watchCount: count});
});
}

if (scope.onDigestLengthUpdate) {
listeners.digestLength.push(function(length) {
scope.onDigestLengthUpdate({digestLength: length});
});
}
}
};

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ng-stats",
"version": "1.0.1",
"version": "1.0.2",
"description": "Little utility to show stats about your page's angular digest/watches.",
"main": "ng-stats.js",
"scripts": {
Expand Down

0 comments on commit 35c8b99

Please sign in to comment.