├── .bowerrc
├── .gitignore
├── .jscsrc
├── .jshintrc
├── .nvmrc
├── .travis.yml
├── Gemfile
├── Gruntfile.js
├── LICENSE
├── README.md
├── angular-viewport-watch.js
├── angular-viewport-watch.sublime-project
├── app
├── images
│ ├── svg-font-icons
│ │ └── wix-logo.svg
│ ├── wixlogo.jpg
│ └── yeoman.png
├── index.html
├── scripts
│ ├── app.js
│ ├── controllers
│ │ └── main.js
│ └── directives
│ │ └── viewport-watch.js
├── styles
│ └── main.scss
└── views
│ └── main.haml
├── bower.json
├── karma.conf.js
├── package.json
├── pom.xml
└── test
├── .jshintrc
├── mock
└── server-api.js
└── spec
├── controllers
└── main.spec.js
├── directives
└── viewport-watch.spec.js
└── services
└── app.spec.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/bower_components"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .tmp
3 | .sass-cache
4 | app/bower_components
5 | *.iml
6 | *.sublime-workspace
7 | npm-debug.log
8 | sauce_connect.log*
9 | .DS_Store
10 | .bundle
11 | .idea
12 | .sauce-connect
13 | vendor
14 | Gemfile.lock
15 | coverage
16 | target
17 | replace.private.conf.js
18 | reference.ts
19 | .baseDir.ts
20 | .tscache
21 | dist
22 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "crockford",
3 | "validateIndentation": 2,
4 | "disallowMultipleVarDecl": null,
5 | "requireMultipleVarDecl": null,
6 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
7 | "disallowDanglingUnderscores": null,
8 | "requireSpaceBeforeObjectValues": true,
9 | "requireVarDeclFirst": null
10 | }
11 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "camelcase": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "immed": true,
7 | "indent": 2,
8 | "latedef": true,
9 | "newcap": true,
10 | "noarg": true,
11 | "quotmark": "single",
12 | "undef": true,
13 | "unused": true,
14 | "strict": true,
15 | "globalstrict": true,
16 | "trailing": true,
17 | "smarttabs": true,
18 | "white": true,
19 | "globals": {
20 | "_": false,
21 | "angular": false,
22 | "require": false,
23 | "module": false
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 6.9.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | install:
6 | - npm install -g npm@2
7 | - npm install
8 | - bundle install
9 |
10 | before_script:
11 | - npm install -g grunt-cli bower
12 | - bower install
13 |
14 | script:
15 | - grunt build:ci
16 |
17 | after_success:
18 | - cat ./coverage/*/lcov.info | ./node_modules/coveralls/bin/coveralls.js
19 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'compass'
4 | gem 'haml'
5 | gem 'scss-lint', '>=0.28.0'
6 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | // Generated on 2014-12-12 using generator-wix-angular 0.1.84
2 | 'use strict';
3 |
4 | module.exports = function (grunt) {
5 | var unitTestFiles = [];
6 | require('./karma.conf.js')({set: function (karmaConf) {
7 | unitTestFiles = karmaConf.files.filter(function (value) {
8 | return value.indexOf('bower_component') !== -1;
9 | });
10 | }});
11 | require('wix-gruntfile')(grunt, {
12 | port: 9000,
13 | preloadModule: 'angularViewportWatchAppInternal',
14 | unitTestFiles: unitTestFiles,
15 | protractor: false,
16 | bowerComponent: true
17 | });
18 |
19 | grunt.modifyTask('yeoman', {
20 | local: 'http://localhost:<%= connect.options.port %>/'
21 | });
22 |
23 | grunt.modifyTask('karma', {
24 | teamcity: {
25 | coverageReporter: {dir: 'coverage/', type: 'lcov'}
26 | }
27 | });
28 |
29 | grunt.modifyTask('copy', {
30 | js: {
31 | expand: true,
32 | cwd: 'dist/scripts',
33 | dest: '',
34 | src: 'angular-viewport-watch.js'
35 | }
36 | });
37 |
38 | grunt.hookTask('build').push('copy:js');
39 |
40 | };
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Wix.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Angular Viewport Watch [](https://travis-ci.org/shahata/angular-viewport-watch) [](https://coveralls.io/r/shahata/angular-viewport-watch?branch=master)
2 | ================
3 |
4 | Boost performance of [Angular](http://www.angularjs.org)'s `ng-repeat` directive for long lists by disabling watchers while elements are not displayed inside viewport.
5 |
6 | Demo: http://shahata.github.io/angular-viewport-watch/
7 |
8 | BTW, `ng-repeat` is just an example, this directive will work on anything.
9 |
10 | ## What it does
11 |
12 | Displaying long lists of items is a big pain in angular since they add many more watchers to the scope which makes the digest loop longer. Since every model change in angular triggers a digest loop, even a simple thing like typing a name inside some input field might become sluggish if a long list of some items is displayed on the page at the same time.
13 |
14 | Angular 1.3 added a bind-once mechanism which removes watchers once they receive a value, but this only helps if the list you are displaying is static. What about cases where your list contains dynamic information which might change at any moment? Bind-once will not help you in those cases.
15 |
16 | This library introduces a simple directive named `viewport-watch` which solves this issue by disabling watchers that are currently out of the viewport and makes sure they get enabled and updated with their correct value the moment they come back into the viewport. This means that at any moment, the amount of items being watched is not greater then the amount of items that fit into the user's screen. This obviously cuts down the digest loop length by orders of magnitude.
17 |
18 | ## Installation
19 |
20 | Install using bower
21 |
22 | `bower install --save angular-viewport-watch`
23 |
24 | Include script tag in your html document.
25 |
26 | ```html
27 |
28 |
29 | ```
30 |
31 | Add a dependency to your application module.
32 |
33 | ```javascript
34 | angular.module('myApp', ['angularViewportWatch']);
35 | ```
36 |
37 | ## Directive Usage
38 |
39 | ```html
40 |
...
41 | ```
42 |
43 | ## Manual watcher toggling
44 |
45 | In some cases you might want to disable or enable the watchers of some scope regardless of its position relative to the view port. This can be done easily by broadcasting an event to this scope (this will effect only scopes that have the `viewport-watch` directive on them):
46 |
47 | ```js
48 | scope.$broadcast('toggleWatchers', false); //turn off watchers
49 | scope.$broadcast('toggleWatchers', true); //turn watchers back on
50 | ```
51 |
52 | ## License
53 |
54 | The MIT License.
55 |
56 | See [LICENSE](https://github.com/shahata/angular-viewport-watch/blob/master/LICENSE)
57 |
--------------------------------------------------------------------------------
/angular-viewport-watch.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | (function() {
4 | viewportWatch.$inject = [ "scrollMonitor", "$timeout" ];
5 | function viewportWatch(scrollMonitor, $timeout) {
6 | var viewportUpdateTimeout;
7 | function debouncedViewportUpdate() {
8 | $timeout.cancel(viewportUpdateTimeout);
9 | viewportUpdateTimeout = $timeout(function() {
10 | scrollMonitor.update();
11 | }, 10);
12 | }
13 | return {
14 | restrict: "AE",
15 | link: function(scope, element, attr) {
16 | var elementWatcher = scrollMonitor.create(element, scope.$eval(attr.viewportWatch || "0"));
17 | function watchDuringDisable() {
18 | this.$$watchersBackup = this.$$watchersBackup || [];
19 | this.$$watchers = this.$$watchersBackup;
20 | var unwatch = this.constructor.prototype.$watch.apply(this, arguments);
21 | this.$$watchers = null;
22 | return unwatch;
23 | }
24 | function toggleWatchers(scope, enable) {
25 | var digest, current, next = scope;
26 | do {
27 | current = next;
28 | if (enable) {
29 | if (current.hasOwnProperty("$$watchersBackup")) {
30 | current.$$watchers = current.$$watchersBackup;
31 | delete current.$$watchersBackup;
32 | delete current.$watch;
33 | digest = !scope.$root.$$phase;
34 | }
35 | } else {
36 | if (!current.hasOwnProperty("$$watchersBackup")) {
37 | current.$$watchersBackup = current.$$watchers;
38 | current.$$watchers = null;
39 | current.$watch = watchDuringDisable;
40 | }
41 | }
42 | next = current.$$childHead;
43 | while (!next && current !== scope) {
44 | if (current.$$nextSibling) {
45 | next = current.$$nextSibling;
46 | } else {
47 | current = current.$parent;
48 | }
49 | }
50 | } while (next);
51 | if (digest) {
52 | scope.$digest();
53 | }
54 | }
55 | function disableDigest() {
56 | toggleWatchers(scope, false);
57 | }
58 | function enableDigest() {
59 | toggleWatchers(scope, true);
60 | }
61 | if (!elementWatcher.isInViewport) {
62 | scope.$evalAsync(disableDigest);
63 | debouncedViewportUpdate();
64 | }
65 | elementWatcher.enterViewport(enableDigest);
66 | elementWatcher.exitViewport(disableDigest);
67 | scope.$on("toggleWatchers", function(event, enable) {
68 | toggleWatchers(scope, enable);
69 | });
70 | scope.$on("$destroy", function() {
71 | elementWatcher.destroy();
72 | debouncedViewportUpdate();
73 | });
74 | }
75 | };
76 | }
77 | angular.module("angularViewportWatch", []).directive("viewportWatch", viewportWatch).value("scrollMonitor", window.scrollMonitor);
78 | })();
--------------------------------------------------------------------------------
/angular-viewport-watch.sublime-project:
--------------------------------------------------------------------------------
1 | {
2 | "folders":
3 | [
4 | {
5 | "path": ".",
6 | "folder_exclude_patterns": [".tmp", ".sass-cache", "dist", "maven", "coverage", "node_modules", ".bundle", "vendor"]
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/app/images/svg-font-icons/wix-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
47 |
--------------------------------------------------------------------------------
/app/images/wixlogo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wix-incubator/angular-viewport-watch/86bca6a04af34db7384aa3034935b826fb240df5/app/images/wixlogo.jpg
--------------------------------------------------------------------------------
/app/images/yeoman.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wix-incubator/angular-viewport-watch/86bca6a04af34db7384aa3034935b826fb240df5/app/images/yeoman.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | angularViewportWatch
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
20 | Watch Count:
21 | Digest Cycle Length:
22 |
23 |
29 |
30 |
31 |
32 | Click cells inside the table or bump button and see digest cycle length. Now toggle the "Hide watchers" checkbox to see the diff
33 | The "hide watchers" directive disables watchers while they
34 | are out of viewport, which shrinks the digest cycle length.
35 | This can be extremely useful when displaying repeaters with
36 | dynamic data, where bind-once cannot help.
37 |
38 |