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.

2
  • 1
    Isolate scopes and ng-model don't mix well: stackoverflow.com/questions/11896732/… Commented Aug 4, 2013 at 1:58
  • @MarkRajcok, thank you, i managed to do it with your help. Commented Aug 4, 2013 at 13:10

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

1 Comment

It feels like a proper way to do this, without any hacks. Thank you.
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.

Comments

0

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

1 Comment

From what I understand, transclude passes the directive's scope into it's content and all child directives inside it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.