3

I've created a custom directive in AngularJS. The directive uses isolated scope, and it somehow prevents the binding for standard ngModel on the same element. I want to create a confirm password field (text for readability in the example).

<input type="text" name="one" ng-model="fields.field_one">
<input type="text" validate-match="fields.field_one" name="two" ng-model="field_two">

My directive invalidates the field, when there is no match.

app.directive('validateMatch', function() {
  return {
    require: 'ngModel',
    scope: { matchValue: '=validateMatch' },
    link: function(scope, elm, attr, ctrl) {
      scope.$watch('matchValue', function(value) {
        ctrl.$setValidity('match', 
            ctrl.$viewValue === value
            || !ctrl.$viewValue && !value);
        });

      function validate(value) {
        ctrl.$setValidity('match', value === scope.matchValue);
        return value;
      }
      ctrl.$parsers.push(validate);
      ctrl.$formatters.push(validate);
    }
  }
});

The thing is, why can't I change the value of that field by changing the model? First field works just fine.

Look at the plunker for details and commented code.

Frizi
  • 2,900
  • 1
  • 19
  • 25

3 Answers3

3

As mentioned in a comment, isolate scopes and ng-model don't mix well. Further, we shouldn't be using an isolate scope here, since we're trying to create a directive/component that needs to interact with another directive (ng-model in this case).

Since the validateMatch directive does not create any new properties, the directive does not need to create any new scope. $parse can be used to get the value of the property that attribute validate-match refers to:

app.directive('validateMatch', function($parse) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, elm, attr, ctrl) {
      var model = $parse(attr.validateMatch);

      // watch for linked field change (field_one)
      scope.$watch(model, function(value) {
        console.log('linked change:', value, ctrl.$viewValue);
        // set valid if equal or both falsy (empty/undefined/null)
        ctrl.$setValidity('match', 
            ctrl.$viewValue === value
            || !ctrl.$viewValue && !value);
      });

      // validate on parse/format (field_two)
      function validate(value) {
        var otherFieldValue = model(scope);
        console.log('validate:', value, otherFieldValue);
        // set valid if equal
        ctrl.$setValidity('match', value === otherFieldValue);
        return value;
      }

      ctrl.$parsers.push(validate);
      ctrl.$formatters.push(validate);
    }
  };
});

plunker

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
1

Following Mark's suggestion, I managed to produce a work-around.

When isolated scope exists on the element, ngModel refers to it. The trick is to look the parent scope from within. You can either change the ngModel by hand (prepending it with $parent.), or automatize this process inside the directive by proper compile function.

This is how i did this:

compile: function(element, attrs, transclude) {
    // reference parent scope, because isolated
    // scopes are not looking up by default
    attrs.$set('ngModel', '$parent.'+attrs.ngModel, false);

    return function(scope, elm, attr, ctrl) {
        // link function body there
    }
}

For the full example, look at this plunk.

Frizi
  • 2,900
  • 1
  • 19
  • 25
0

From what I understand of AngularJS Directives you can use the transclude parameter to access the parent scope of the controller.

Bazmo
  • 46
  • 9
  • From what I understand, transclude passes the directive's scope into it's content and all child directives inside it. – Frizi Nov 06 '13 at 21:54