├── .gitignore
├── LICENSE
├── README.md
├── angular-password.js
├── angular-password.min.js
├── bower.json
├── gulpfile.js
├── karma.conf.js
├── package.json
└── tests
└── angular-password.spec.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Deployed apps should consider commenting this line out:
24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
25 | node_modules
26 | bower_components
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 PatrickJS
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-password
2 | ================
3 |
4 | The most performant AngularJS directive for matching two password input fields. I use angular's built in $parsers rather than relying on a $watch
5 |
6 | Installation
7 |
8 | `bower install angular-password`
9 |
10 | or
11 |
12 | `npm install angular-password`
13 |
14 | Inject angular-password into your module
15 |
16 | ```javascript
17 | angular.module('yourmodulename', ['ngPassword']);
18 | ```
19 |
20 | Simple example.
21 |
22 | ```html
23 |
24 |
25 | ```
26 | With ngMessages
27 | ```html
28 |
48 | ```
49 |
50 | Licensing information can be found [here](LICENSE)
51 |
--------------------------------------------------------------------------------
/angular-password.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 |
4 | function $Password() {
5 |
6 | function link(scope, element, attrs, ctrls) {
7 | var formController = ctrls[1];
8 | var ngModel = ctrls[0];
9 | var otherPasswordModel = formController[attrs.matchPassword];
10 |
11 | var getMatchValue = function() {
12 | return otherPasswordModel.$viewValue;
13 | };
14 |
15 | scope.$watch(getMatchValue, function() {
16 | ngModel.$$parseAndValidate();
17 | });
18 |
19 | // if ng1.3+
20 | if (ngModel.$validators) {
21 | ngModel.$validators.passwordMatch = function(modelValue) {
22 | return (!modelValue && !otherPasswordModel.$modelValue) || (modelValue === otherPasswordModel.$modelValue);
23 | };
24 | } else {
25 | ngModel.$parsers.push(function(value) {
26 | ngModel.$setValidity('passwordMatch', (!value && !otherPasswordModel.$viewValue) || value === otherPasswordModel.$viewValue);
27 | return value;
28 | });
29 | }
30 |
31 | otherPasswordModel.$parsers.push(function(value) {
32 | ngModel.$setValidity('passwordMatch', (!value && !ngModel.$viewValue) || value === ngModel.$viewValue);
33 | return value;
34 | });
35 | }
36 |
37 | var controllers = ['^ngModel', '^form'];
38 |
39 | return {
40 | restrict: 'A',
41 | require: controllers,
42 | link: link
43 | }; // end return
44 | }
45 |
46 | angular.module('ngPassword', []).directive('matchPassword', $Password);
47 |
48 | angular.module('angular.password', ['ngPassword']);
49 | angular.module('angular-password', ['ngPassword']);
50 |
51 | if (typeof module === 'object' && typeof define !== 'function') {
52 | module.exports = angular.module('ngPassword');
53 | }
54 | })();
55 |
--------------------------------------------------------------------------------
/angular-password.min.js:
--------------------------------------------------------------------------------
1 | !function(){"use strict";function a(){function a(a,e,r,n){var s=n[1],u=n[0],o=s[r.matchPassword],t=function(){return o.$viewValue};a.$watch(t,function(){u.$$parseAndValidate()}),u.$validators?u.$validators.passwordMatch=function(a){return!a&&!o.$modelValue||a===o.$modelValue}:u.$parsers.push(function(a){return u.$setValidity("passwordMatch",!a&&!o.$viewValue||a===o.$viewValue),a}),o.$parsers.push(function(a){return u.$setValidity("passwordMatch",!a&&!u.$viewValue||a===u.$viewValue),a})}var e=["^ngModel","^form"];return{restrict:"A",require:e,link:a}}angular.module("ngPassword",[]).directive("matchPassword",a),angular.module("angular.password",["ngPassword"]),angular.module("angular-password",["ngPassword"]),"object"==typeof module&&"function"!=typeof define&&(module.exports=angular.module("ngPassword"))}();
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-password",
3 | "main": "angular-password.js",
4 | "version": "1.0.3",
5 | "homepage": "https://github.com/gdi2290/angular-password",
6 | "description": "The most performant AngularJS directive for matching two password input fields ",
7 | "keywords": [
8 | "angular",
9 | "angularjs",
10 | "password",
11 | "match password",
12 | "confirm password",
13 | "passwords",
14 | "angular-password",
15 | "ngPassword",
16 | "gdi2290",
17 | "PatrickJS"
18 | ],
19 | "authors": [
20 | "gdi2290 "
21 | ],
22 | "license": "MIT",
23 | "ignore": [
24 | "**/.*",
25 | "node_modules",
26 | "bower_components",
27 | "test",
28 | "tests"
29 | ],
30 | "dependencies" : {
31 | "angular" : "*"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp'),
2 | jshint = require('gulp-jshint'),
3 | uglify = require('gulp-uglify'),
4 | rename = require('gulp-rename'),
5 | KarmaServer = require('karma').Server;
6 |
7 |
8 | gulp.task('jshint', function() {
9 | return gulp.src('./angular-password.js')
10 | .pipe(jshint())
11 | .pipe(jshint.reporter('default'), {verbose: true})
12 | .pipe(jshint.reporter('fail'));
13 | });
14 |
15 | gulp.task('test', gulp.series('jshint', function(done) {
16 | new KarmaServer({
17 | configFile: __dirname + '/karma.conf.js',
18 | singleRun: true
19 | }, done).start();
20 | }));
21 |
22 | gulp.task('dist', gulp.series('test', function() {
23 | return gulp.src('./angular-password.js')
24 | .pipe(uglify())
25 | .pipe(rename('angular-password.min.js'))
26 | .pipe(gulp.dest('./'));
27 | }));
28 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Sun Mar 27 2016 03:17:28 GMT-0400 (Eastern Daylight Time)
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'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | './node_modules/angular/angular.js',
19 | './node_modules/angular-mocks/angular-mocks.js',
20 | './angular-password.js',
21 | 'tests/*.js'
22 | ],
23 |
24 |
25 | // list of files to exclude
26 | exclude: [
27 | ],
28 |
29 |
30 | // preprocess matching files before serving them to the browser
31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
32 | preprocessors: {
33 | },
34 |
35 |
36 | // test results reporter to use
37 | // possible values: 'dots', 'progress'
38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
39 | reporters: ['progress'],
40 |
41 |
42 | // web server port
43 | port: 9876,
44 |
45 |
46 | // enable / disable colors in the output (reporters and logs)
47 | colors: true,
48 |
49 |
50 | // level of logging
51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
52 | logLevel: config.LOG_INFO,
53 |
54 |
55 | // enable / disable watching file and executing tests whenever any file changes
56 | autoWatch: true,
57 |
58 |
59 | // start these browsers
60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
61 | browsers: ['PhantomJS'],
62 |
63 |
64 | // Continuous Integration mode
65 | // if true, Karma captures browsers, runs the tests and exits
66 | singleRun: false,
67 |
68 | // Concurrency level
69 | // how many browser should be started simultaneous
70 | concurrency: Infinity
71 | });
72 | };
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-password",
3 | "version": "1.0.3",
4 | "description": "The most performant AngularJS directive for matching two password input fields ",
5 | "main": "angular-password.js",
6 | "keywords": [
7 | "angular",
8 | "angularjs",
9 | "password",
10 | "match password",
11 | "confirm password",
12 | "passwords",
13 | "angular-password",
14 | "ngPassword",
15 | "gdi2290",
16 | "PatrickJS"
17 | ],
18 | "scripts": {
19 | "test": "gulp test"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/gdi2290/angular-password.git"
24 | },
25 | "author": "gdi2290 ",
26 | "license": "MIT",
27 | "bugs": {
28 | "url": "https://github.com/gdi2290/angular-password/issues"
29 | },
30 | "homepage": "https://github.com/gdi2290/angular-password",
31 | "dependencies": {
32 | "angular": "^1.5.5"
33 | },
34 | "devDependencies": {
35 | "angular-mocks": "^1.5.5",
36 | "gulp": "^4.0.0",
37 | "gulp-jshint": "^2.1.0",
38 | "gulp-rename": "^1.3.0",
39 | "gulp-uglify": "^3.0.0",
40 | "jasmine-core": "^2.4.1",
41 | "jshint": "^2.9.5",
42 | "karma": "^0.13.22",
43 | "karma-jasmine": "^0.3.8",
44 | "karma-phantomjs-launcher": "^1.0.0",
45 | "karma-spec-reporter": "0.0.32",
46 | "phantomjs-prebuilt": "^2.1.16"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/angular-password.spec.js:
--------------------------------------------------------------------------------
1 | describe('angular-password', function() {
2 |
3 | var $scope, form;
4 |
5 | beforeEach(module('ngPassword'));
6 |
7 | beforeEach(inject(function($compile, $rootScope) {
8 | $scope = $rootScope;
9 |
10 | var element = angular.element(
11 | ''
15 | );
16 |
17 | $compile(element)($scope);
18 |
19 | $scope.model = {};
20 | form = $scope.form;
21 | }));
22 |
23 |
24 | describe('match-password', function() {
25 |
26 | // simulates initial page load with nothing in the models
27 | it('should pass with no password or confirm', function() {
28 | $scope.$digest();
29 |
30 | expect($scope.model.password).toBeUndefined();
31 | expect($scope.model.confirm).toBeUndefined();
32 | expect(form.password.$valid).toBe(true);
33 | expect(form.confirm.$valid).toBe(true);
34 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
35 | });
36 |
37 | // simulates typing something into password then deleting it
38 | it('should pass with empty password and no confirm', function() {
39 | form.password.$setViewValue('');
40 | $scope.$digest();
41 |
42 | expect($scope.model.password).toBe('');
43 | expect($scope.model.confirm).toBeUndefined();
44 | expect(form.password.$valid).toBe(true);
45 | expect(form.confirm.$valid).toBe(true);
46 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
47 | });
48 |
49 | // simulates typing something into confirm then deleting it
50 | it('should pass with no password and empty confirm', function() {
51 | form.confirm.$setViewValue('');
52 | $scope.$digest();
53 |
54 | expect($scope.model.password).toBeUndefined();
55 | expect($scope.model.confirm).toBe('');
56 | expect(form.password.$valid).toBe(true);
57 | expect(form.confirm.$valid).toBe(true);
58 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
59 | });
60 |
61 | // simulates typing into both fields and deleting them
62 | it('should pass with empty password and confirm', function() {
63 | form.password.$setViewValue('');
64 | form.confirm.$setViewValue('');
65 | $scope.$digest();
66 |
67 | expect($scope.model.password).toBe('');
68 | expect($scope.model.confirm).toBe('');
69 | expect(form.password.$valid).toBe(true);
70 | expect(form.confirm.$valid).toBe(true);
71 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
72 | });
73 |
74 | // simulates typing into password but not confirm
75 | it('should fail with password set and no confirm', function() {
76 | form.password.$setViewValue('abcdef');
77 | $scope.$digest();
78 |
79 | expect($scope.model.password).toBe('abcdef');
80 | expect($scope.model.confirm).toBeUndefined();
81 | expect(form.password.$valid).toBe(true);
82 | expect(form.confirm.$valid).toBe(false);
83 | expect(form.confirm.$error.passwordMatch).toBe(true);
84 | });
85 |
86 | // simulates typing into both fields then deleting confirm
87 | it('should fail with password set and empty confirm', function() {
88 | form.password.$setViewValue('abcdef');
89 | form.confirm.$setViewValue('');
90 | $scope.$digest();
91 |
92 | expect($scope.model.password).toBe('abcdef');
93 | expect($scope.model.confirm).toBeUndefined();
94 | expect(form.password.$valid).toBe(true);
95 | expect(form.confirm.$valid).toBe(false);
96 | expect(form.confirm.$error.passwordMatch).toBe(true);
97 | });
98 |
99 | // simulates typing into both fields then deleting password
100 | it('should fail with confirm set and empty password', function() {
101 | form.password.$setViewValue('');
102 | form.confirm.$setViewValue('abcdef');
103 | $scope.$digest();
104 |
105 | expect($scope.model.password).toBe('');
106 | expect($scope.model.confirm).toBeUndefined();
107 | expect(form.password.$valid).toBe(true);
108 | expect(form.confirm.$valid).toBe(false);
109 | expect(form.confirm.$error.passwordMatch).toBe(true);
110 | });
111 |
112 | // simulates typing password, then confirm, not matching
113 | it('should fail with password set and then different confirm', function() {
114 | form.password.$setViewValue('abcdef');
115 | form.confirm.$setViewValue('fedcba');
116 | $scope.$digest();
117 |
118 | expect($scope.model.password).toBe('abcdef');
119 | expect($scope.model.confirm).toBeUndefined();
120 | expect(form.password.$valid).toBe(true);
121 | expect(form.confirm.$valid).toBe(false);
122 | expect(form.confirm.$error.passwordMatch).toBe(true);
123 | });
124 |
125 | // simulates typing confirm, then password, not matching
126 | it('should fail with confirm set and then different password', function() {
127 | form.confirm.$setViewValue('fedcba');
128 | form.password.$setViewValue('abcdef');
129 | $scope.$digest();
130 |
131 | expect($scope.model.password).toBe('abcdef');
132 | expect($scope.model.confirm).toBeUndefined();
133 | expect(form.password.$valid).toBe(true);
134 | expect(form.confirm.$valid).toBe(false);
135 | expect(form.confirm.$error.passwordMatch).toBe(true);
136 | });
137 |
138 | // simulates typing password, then confirm, then changing password
139 | it('should fail with both set same then password changed', function() {
140 | form.password.$setViewValue('abcdef');
141 | form.confirm.$setViewValue('abcdef');
142 | $scope.$digest();
143 |
144 | form.password.$setViewValue('aaaaaa');
145 | $scope.$digest();
146 |
147 | expect($scope.model.password).toBe('aaaaaa');
148 | expect($scope.model.confirm).toBeUndefined();
149 | expect(form.password.$valid).toBe(true);
150 | expect(form.confirm.$valid).toBe(false);
151 | expect(form.confirm.$error.passwordMatch).toBe(true);
152 | });
153 |
154 | // simulates typing confirm, then password, then changing confirm
155 | it('should fail with both set same then confirm changed', function() {
156 | form.confirm.$setViewValue('abcdef');
157 | form.password.$setViewValue('abcdef');
158 | $scope.$digest();
159 |
160 | form.confirm.$setViewValue('aaaaaa');
161 | $scope.$digest();
162 |
163 | expect($scope.model.password).toBe('abcdef');
164 | expect($scope.model.confirm).toBeUndefined();
165 | expect(form.password.$valid).toBe(true);
166 | expect(form.confirm.$valid).toBe(false);
167 | expect(form.confirm.$error.passwordMatch).toBe(true);
168 | });
169 |
170 | // simulates typing password, then confirm, not matching, then changing password to match
171 | it('should pass with both set differently then password changed to match', function() {
172 | form.password.$setViewValue('aaaaaa');
173 | form.confirm.$setViewValue('abcdef');
174 | $scope.$digest();
175 |
176 | form.password.$setViewValue('abcdef');
177 | $scope.$digest();
178 |
179 | expect($scope.model.password).toBe('abcdef');
180 | expect($scope.model.confirm).toBe('abcdef');
181 | expect(form.password.$valid).toBe(true);
182 | expect(form.confirm.$valid).toBe(true);
183 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
184 | });
185 |
186 | // simulates typing confirm, then password, not matching, then changing confirm to match
187 | it('should pass with both set differently then confirm changed to match', function() {
188 | form.confirm.$setViewValue('abcdef');
189 | form.password.$setViewValue('aaaaaa');
190 | $scope.$digest();
191 |
192 | form.confirm.$setViewValue('aaaaaa');
193 | $scope.$digest();
194 |
195 | expect($scope.model.password).toBe('aaaaaa');
196 | expect($scope.model.confirm).toBe('aaaaaa');
197 | expect(form.password.$valid).toBe(true);
198 | expect(form.confirm.$valid).toBe(true);
199 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
200 | });
201 |
202 | // simulates typing password, then confirm, matching
203 | it('should pass with password then confirm matching', function() {
204 | form.password.$setViewValue('abcdef');
205 | form.confirm.$setViewValue('abcdef');
206 | $scope.$digest();
207 |
208 | expect($scope.model.password).toBe('abcdef');
209 | expect($scope.model.confirm).toBe('abcdef');
210 | expect(form.password.$valid).toBe(true);
211 | expect(form.confirm.$valid).toBe(true);
212 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
213 | });
214 |
215 | // simulates typing confirm, then password, matching
216 | it('should pass with password then confirm matching', function() {
217 | form.confirm.$setViewValue('abcdef');
218 | form.password.$setViewValue('abcdef');
219 | $scope.$digest();
220 |
221 | expect($scope.model.password).toBe('abcdef');
222 | expect($scope.model.confirm).toBe('abcdef');
223 | expect(form.password.$valid).toBe(true);
224 | expect(form.confirm.$valid).toBe(true);
225 | expect(form.confirm.$error.passwordMatch).toBeUndefined();
226 | });
227 |
228 | });
229 | });
230 |
--------------------------------------------------------------------------------