├── .gitignore
├── .travis.yml
├── Gulpfile.js
├── LICENSE
├── README.md
├── angular-fng.js
├── angular-fng.min.js
├── bower.json
├── karma.conf.js
├── package.json
└── test
├── angular-fng.spec.js
├── demo.html
└── test-main.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdamCraven/angular-fng/52d28d89cdcc4054514bb1b66d96cf2dcbec0702/.travis.yml
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | var karma = require('karma').server;
2 | var gulp = require('gulp');
3 | var uglify = require('gulp-uglify');
4 | var rename = require('gulp-rename');
5 |
6 | gulp.task('default', function() {
7 | karma.start({
8 | configFile: __dirname + '/karma.conf.js',
9 | action: 'watch'
10 | });
11 | });
12 |
13 | gulp.task('build', function() {
14 | return gulp.src('./angular-fng.js')
15 | .pipe(rename('./angular-fng.min.js'))
16 | .pipe(uglify())
17 | .pipe(gulp.dest('.'))
18 | });
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
4 | Copyright (c) 2015 Adam Craven
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # angular-fng (**F**aster a**NG**ular)
3 |
4 | Performance focused event directives, that act the same as the ng-event directives (ng-click, ng-mousedown, etc.). But instead of triggering a global root scope digest, it can trigger a partial scope digest, increasing performance and responsiveness.
5 |
6 | Example: Simulated large app (Greater than 1000 watchers)
7 |
8 |
9 |
10 |
11 | LEFT: Using ng-event. RIGHT: Using fng-event, not refreshing all the watchers in an app.
12 |
13 | New directives defined, which can be used as a replacement or in addition to the default directives:
14 |
15 | * fng-click
16 | * fng-dblclick
17 | * fng-mousedown
18 | * fng-mouseup
19 | * fng-mouseover
20 | * fng-mouseout
21 | * fng-mousemove
22 | * fng-mouseenter
23 | * fng-mouseleave
24 | * fng-keydown
25 | * fng-keyup
26 | * fng-keypress
27 | * fng-submit
28 | * fng-focus
29 | * fng-blur
30 | * fng-copy
31 | * fng-cut
32 | * fng-paste
33 |
34 |
35 | ## Why
36 |
37 | Not sure what's it all about? Have a read of: [angular-fng - Improve the performance of large angular 1.x apps, by using faster event directives](https://code.adamcrvn.com/increasing-performance-on-large-angular-apps/)
38 |
39 | ## Requirements
40 |
41 | * Angular 1.2.0 or greater - May work on older versions.
42 |
43 | ## Installation
44 |
45 | * bower: `bower install angular-fng --save`
46 | * npm: `npm install angular-fng --save`
47 | * Or download from github: [angular-fng.zip](https://github.com/AdamCraven/angular-fng/archive/master.zip)
48 |
49 | Include angular-fng after angular.js has been loaded.
50 |
51 | ```html
52 |
53 | ```
54 |
55 | Or can be required in via require.js or other module loaders which support CommonJS or AMD module definition, just be sure that angular is loaded first
56 |
57 | ## Usage
58 |
59 | To enable add fng to your main module:
60 |
61 | ```js
62 | angular.module('myApp', ['fng'])
63 | ```
64 |
65 | Enable partial digesting by setting $stopDigestPropagation on your chosen scope:
66 |
67 | ```js
68 | $chosenScope.$stopDigestPropagation = true
69 | ```
70 |
71 | Then replace all uses of the ng-event directives with fng:
72 | ```html
73 | Click Me
74 | ```
75 |
76 | When clicked, the digest will occur from the $chosenScope.
77 |
78 | ### How it works
79 |
80 | The fng events are opt-in directives, which behave *the same* as an ng event directive. However, it differs in one important way. When triggered (e.g. fng-click) it bubbles up the scope tree and searches for a defined $stopDigestPropagation property.
81 |
82 | When found it will call a $digest in the scope where $stopDigestPropagation is set and checks all the child scopes as shown below:
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | If $stopDigestPropagation property isn't found, it will fallback to the default behaviour and act **the same** as the ng-event directives, calling a root scope digest:
92 |
93 |
94 |
95 |
96 |
97 |
98 | Because they work the same as the existing ng-event directives, they can be dropped in and used as a replacement.
99 | That means all ng-keydowns can be converted to fng-keydowns, and so forth.
100 |
101 |
102 | ### How to chose where to digest
103 |
104 | It is not recommended that these are used at low levels, such as in individual components. The live search component, mentioned in [angular-fng - Improve the performance of large angular 1.x apps, by using faster event directives](https://code.adamcrvn.com/increasing-performance-on-large-angular-apps/), would not implement $stopDigestPropagation property. It should be implemented at the module level, or higher. Such as a group of modules that relate to a major aspect of functionality on a page.
105 |
--------------------------------------------------------------------------------
/angular-fng.js:
--------------------------------------------------------------------------------
1 | // Angular fng / MIT License
2 | (function() {
3 | 'use strict';
4 |
5 | // Refer to https://github.com/angular/angular.js/blob/master/src/ng/directive/ngEventDirs.js
6 | // and angular code base for much of the originating source code.
7 | // There are many private methods in angular, duplication has been necessary.
8 |
9 | var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
10 | var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
11 |
12 | /**
13 | * Converts all accepted directives format into proper directive name.
14 | * @param name Name to normalize
15 | */
16 | function directiveNormalize(name) {
17 | return camelCase(name.replace(PREFIX_REGEXP, ''));
18 | }
19 |
20 | /**
21 | * Converts snake_case to camelCase.
22 | * Also there is special case for Moz prefix starting with upper case letter.
23 | * @param name Name to normalize
24 | */
25 | function camelCase(name) {
26 | return name.
27 | replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
28 | return offset ? letter.toUpperCase() : letter;
29 | });
30 | }
31 |
32 | /**
33 | * Bubbles up the scope tree recursively to check if stopDigestPropagation is set on scope
34 | * returns that scope if defined otherwise undefined
35 | * @param {object} scope Where scope originated
36 | * @return {object} Scope to partial digest in or undefined
37 | */
38 | function findPartialScope(scope) {
39 | if (scope.hasOwnProperty('$stopDigestPropagation')) {
40 | return scope;
41 | } else if (scope.$parent) {
42 | return findPartialScope(scope.$parent);
43 | }
44 | return;
45 | }
46 |
47 | function partialDigest(scope, callback) {
48 | callback();
49 | scope.$digest();
50 | }
51 |
52 | function fullDigest (scope, callback) {
53 | scope.$apply(callback);
54 | }
55 |
56 | // For events that might fire synchronously during DOM manipulation
57 | // we need to execute their event handlers asynchronously using $evalAsync,
58 | // so that they are not executed in an inconsistent state.
59 | var forceAsyncEvents = {
60 | 'blur': true,
61 | 'focus': true
62 | };
63 | var events = 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' ');
64 |
65 | function fng(angular) {
66 | var fngModule = angular.module('fng', []);
67 |
68 | function assignDirectives(eventName) {
69 | var directiveName = directiveNormalize('fng-' + eventName);
70 | fngModule.directive(directiveName, ['$parse', '$rootScope', function($parse, $rootScope) {
71 | return {
72 | restrict: 'A',
73 | compile: function($element, attr) {
74 | // We expose the powerful $event object on the scope that provides access to the Window,
75 | // etc. that isn't protected by the fast paths in $parse. We explicitly request better
76 | // checks at the cost of speed since event handler expressions are not executed as
77 | // frequently as regular change detection.
78 | var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true);
79 | return function ngEventHandler(scope, element) {
80 | element.on(eventName, function fngEventHandler(event) {
81 | var partialDigestScope;
82 | var callback = function() {
83 | fn(scope, {
84 | $event: event
85 | });
86 | };
87 |
88 | if (forceAsyncEvents[eventName] && $rootScope.$$phase) {
89 | scope.$evalAsync(callback);
90 | } else if ((partialDigestScope = findPartialScope(scope))) {
91 | partialDigest(partialDigestScope, callback);
92 | } else {
93 | fullDigest(scope, callback);
94 | }
95 | });
96 | };
97 | }
98 | };
99 | }]);
100 | }
101 | events.forEach(assignDirectives);
102 | }
103 |
104 | if (typeof define === 'function' && define.amd) {
105 | define(['angular'], fng);
106 | } else if (typeof module !== 'undefined' && module && module.exports) {
107 | fng(angular);
108 | module.exports = 'fng';
109 | } else {
110 | fng(angular);
111 | }
112 |
113 | })();
--------------------------------------------------------------------------------
/angular-fng.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";function e(e){return n(e.replace(i,""))}function n(e){return e.replace(c,function(e,n,o,u){return u?o.toUpperCase():o})}function o(e){return e.hasOwnProperty("$stopDigestPropagation")?e:e.$parent?o(e.$parent):void 0}function u(e,n){n(),e.$digest()}function t(e,n){e.$apply(n)}function r(n){function r(n){var r=e("fng-"+n);i.directive(r,["$parse","$rootScope",function(e,i){return{restrict:"A",compile:function(c,f){var s=e(f[r],null,!0);return function(e,r){r.on(n,function(r){var c,f=function(){s(e,{$event:r})};a[n]&&i.$$phase?e.$evalAsync(f):(c=o(e))?u(c,f):t(e,f)})}}}}])}var i=n.module("fng",[]);f.forEach(r)}var i=/^((?:x|data)[\:\-_])/i,c=/([\:\-\_]+(.))/g,a={blur:!0,focus:!0},f="click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" ");"function"==typeof define&&define.amd?define(["angular"],r):"undefined"!=typeof module&&module&&module.exports?(r(angular),module.exports="fng"):r(angular)}();
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-fng",
3 | "description": "Performance focused event directives, that act the same as the ng-event directives (ng-click, ng-mousedown, etc.). But instead of triggering a global root scope digest, it can trigger a partial scope digest, increasing performance and responsiveness.",
4 | "author": "Adam Craven",
5 | "license": "MIT",
6 | "homepage": "https://github.com/AdamCraven/angular-fng",
7 | "main": "./angular-fng.js",
8 | "ignore": [
9 | ],
10 | "dependencies": {
11 | "angular": ">=1.2.0 <2.0"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git://github.com/AdamCraven/angular-fng.git"
16 | }
17 | }
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Wed Jun 10 2015 18:47:59 GMT+0100 (BST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['jasmine', 'requirejs'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | {pattern: 'node_modules/angular/angular.js', included: false},
19 | {pattern: 'node_modules/angular-mocks/angular-mocks.js', included: false},
20 | {pattern: 'node_modules/jquery/dist/jquery.min.js', included: false},
21 | {pattern: 'test/*.spec.js', included: false},
22 | {pattern: 'angular-fng.js', included: false},
23 | 'test/test-main.js',
24 | ],
25 |
26 |
27 | // list of files to exclude
28 | exclude: [
29 | ],
30 |
31 |
32 | // preprocess matching files before serving them to the browser
33 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
34 | preprocessors: {
35 | },
36 |
37 |
38 | // test results reporter to use
39 | // possible values: 'dots', 'progress'
40 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
41 | reporters: ['progress'],
42 |
43 |
44 | // web server port
45 | port: 9876,
46 |
47 |
48 | // enable / disable colors in the output (reporters and logs)
49 | colors: true,
50 |
51 |
52 | // level of logging
53 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
54 | logLevel: config.LOG_INFO,
55 |
56 |
57 | // enable / disable watching file and executing tests whenever any file changes
58 | autoWatch: true,
59 |
60 |
61 | // start these browsers
62 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
63 | browsers: ['Chrome'],
64 |
65 |
66 | // Continuous Integration mode
67 | // if true, Karma captures browsers, runs the tests and exits
68 | singleRun: false
69 | });
70 | };
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-fng",
3 | "version": "1.0.4",
4 | "description": "Performance focused event directives, that act the same as the ng-event directives (ng-click, ng-mousedown, etc.). But instead of triggering a global root scope digest, it can trigger a partial scope digest, increasing performance and responsiveness.",
5 | "main": "angular-fng.js",
6 | "scripts": {
7 | "test": "gulp"
8 | },
9 | "license": "MIT",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/AdamCraven/angular-fng"
13 | },
14 | "devDependencies": {
15 | "angular": "1.4.0",
16 | "angular-mocks": "1.4.0",
17 | "gulp": "^3.9.0",
18 | "gulp-karma": "0.0.4",
19 | "gulp-rename": "^1.2.2",
20 | "gulp-uglify": "^1.2.0",
21 | "gulp-umd": "^0.2.0",
22 | "jquery": "^2.1.4",
23 | "karma": "^0.12.36",
24 | "karma-chrome-launcher": "^0.1.12",
25 | "karma-jasmine": "0.1.5",
26 | "karma-requirejs": "^0.2.2",
27 | "requirejs": "^2.1.18"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/test/angular-fng.spec.js:
--------------------------------------------------------------------------------
1 | define(['angular', 'angular-mocks', '../angular-fng.js'], function(angular, angularMocks, angularFng) {
2 | 'use strict';
3 |
4 | beforeEach(function() {
5 | module('fng');
6 | });
7 |
8 | describe('standard angular tests for event directives', function() {
9 | var element;
10 |
11 | describe('fngSubmit', function() {
12 |
13 | it('should get called on form submit', inject(function($rootScope, $compile) {
14 | element = $compile('