Search code examples
angularjsangular-directiveng-pattern

Custom notPattern directive like ngPattern, but checking whether a RegExp does not match


I am trying to write a custom AngularJS directive similar to ngPattern, except that this directive would fail validation if a RegExp does not match.

Following the source of the ngPattern directive, I came up with:

  .directive("notPattern", function () {
    return {
      restrict: "A",
      require: "?ngModel",
      link: function (scope, elem, attr, ctrl) {
        if (!ctrl) return;

        var regExp, notPatternExp = attr.notPattern;
        attr.$observe("notPattern", function (regex) {
          if (angular.isString(regex) && regex.length > 0) {
            regex = new RegExp("^" + regex + "$");
          }

          if (regex && !regex.test) {
            var elemClone = angular.element(elem).clone();
            var elemHtml = angular.element("<div>").append(elemClone).html();
            throw new Error("Expected " + notPatternExp + " to be a RegExp but was " + regex + ". Element: " + elemHtml);
          }

          regExp = regex || undefined;
          ctrl.$validate();
        });

        ctrl.$validators.notpattern = function (value) {
          return ctrl.$isEmpty(value) || angular.isUndefined(regExp) || !regExp.test(value);
        };
      }
    };
  })

This works when I specify the regular expression source within the attribute, but I want to allow a RegExp object to be used directly, like ngPattern.

The problem is that the expression is not being evaluated.

Here is an example JSFiddle:
http://jsfiddle.net/8Lk3pqep/1/

In this example, entering anything other than "abc" into the text field will show "Invalid per 'pattern'!". I expected to see "Invalid per 'notpattern'!" when "abc" is entered, but this appears only when I enter "testRegExp", thus indicating that the attribute value is not being evaluated.

What am I doing wrong?

I know that notPattern can be accomplished with ngPattern via something like:

$scope.testRegExp = {
  test: function (value) {
    return !/^abc$/.test(value);
  }
};

.. but I would like to know why the custom directive is not evaluating the expression.


Solution

  • It turns out that ngPattern is a special case within Angular of an "aliased input attr", which means that a watch is set up to evaluate the expression via a higher-priority directive. The watch listener function simply sets the attribute to the evaluated result each time:
    https://github.com/angular/angular.js/blob/bfcf9946e16d21b55dde50d4d21c71c898b10215/src/ng/directive/attrs.js#L377

    To fix the notPattern directive, instead of observing the notPattern attribute, set up a watch of the notPattern expression to mirror the built-in set up for the ngPattern attribute:

      .directive("notPattern", function () {
        return {
          restrict: "A",
          require: "?ngModel",
          link: function (scope, elem, attr, ctrl) {
            if (!ctrl) return;
    
            var regExp, notPatternExp = attr.notPattern;
            scope.$watch(notPatternExp, function (regex) {
              if (angular.isString(regex) && regex.length > 0) {
                regex = new RegExp("^" + regex + "$");
              }
    
              if (regex && !regex.test) {
                var elemClone = angular.element(elem).clone();
                var elemHtml = angular.element("<div>").append(elemClone).html();
                throw new Error("Expected " + notPatternExp + " to be a RegExp but was " + regex + ". Element: " + elemHtml);
              }
    
              regExp = regex || undefined;
              ctrl.$validate();
            });
    
            ctrl.$validators.notpattern = function (value) {
              return ctrl.$isEmpty(value) || angular.isUndefined(regExp) || !regExp.test(value);
            };
          }
        };
      })
    

    Here is an updated JSFiddle:
    http://jsfiddle.net/8Lk3pqep/2/