Input Matcher

This is a small demo for the input matcher. It showcases using it and displaying custom errors for the specific case where they do not match.

by Evan Sharp

HTML

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
<div data-ng-app="myApp">
    <p>
        This is a very basic example/demo.<br />
        It will only fire after the confirm field has been modified (is no longer pristine)
    </p>
    <hr />
    <form name="myForm">
        Password: <input ng-model="user.password" type="text" name="password" data-ng-class="{'ng-invalid':myForm.confirmPassword.$error.match}" />
        Confirm: <input ng-model="user.passwordConfirm" type="text" data-match="user.password" name="confirmPassword" />
        <div data-ng-show="myForm.confirmPassword.$error.match">Passwords do not match!</div>
    </form>
    <hr />
    <p>Mis-match? {{myForm.confirmPassword.$error.match}}</p>
    <p>Internal password value: {{user.password}}</p>
    <p>Internal confirm value: {{user.passwordConfirm}}</p>
</div>

CSS

input{
    border:1px solid black;
    outline:none;
}
input.ng-invalid{
    border-color: red;
}

JavaScript

'use strict';

angular.module('myApp', []).directive('match', match);

function match ($parse) {
    return {
        require: '?ngModel',
        restrict: 'A',
        link: function(scope, elem, attrs, ctrl) {
            if(!ctrl) {
                console && console.warn('Match validation requires ngModel to be on the element');
                return;
            }
            
            var matchGetter = $parse(attrs.match);
            var modelSetter = $parse(attrs.ngModel).assign;

            scope.$watch(attrs.match, function(){
                modelSetter(scope, parser(ctrl.$viewValue));
            });

            ctrl.$parsers.unshift(parser);
            ctrl.$formatters.unshift(formatter);
            
            function parser(viewValue){
                if((ctrl.$pristine && ctrl.$isEmpty(viewValue)) || viewValue === matchGetter(scope)){
                    ctrl.$setValidity('match', true);
                    return viewValue;
                }else{
                    ctrl.$setValidity('match', false);
                    return undefined;
                }
            }
            
            function formatter(modelValue){
                return modelValue === undefined? ctrl.$isEmpty(ctrl.$viewValue)? undefined : ctrl.$viewValue : modelValue;
            }
        }
    };
}