1

I am using custom angular form validation to validate fields based off the field having a value AND the selected value of a drop down. I use the same section of code (and therefore the same directive) twice on the page. So I could do this re-use, I tried sending in an argument to the directive. This is all working fine. However when the child scope is created it breaks my 2-way binding back to fruit.cost.

Here is an example fiddle.

What am I doing wrong? I want all the validation to work the same but also preserve the 2-way binding. Here is a copy of my fiddle code:

JS

function MainCtrl($scope){
  $scope.localFruits = [
      {name: 'banana'},
      {name: 'orange'},
      {name: 'grape'}
  ];
  $scope.fruits = [
      {name: 'banana'},
      {name: 'orange'},
      {name: 'grape'}
  ];
  $scope.costRequired = [
      'local',
      'overseas'
  ];
  $scope.selectedCostRequired = '';
}

angular.module('test', []).directive('customRequired', function() {
  return {
      restrict: 'A',
      require: 'ngModel',
      scope: {
          requiredWhenKey: '=',
          cost: '=' // I think the problem is here?? Can't figure out how to pass the property I want to bind to
      },
      link: function(scope, elem, attrs, ctrl) {
          //console.log(scope);
          function isValid(value) {
              if (scope.$parent.selectedCostRequired === scope.requiredWhenKey) {
                  return !!value;
              }
              return true;
          }

          ctrl.$parsers.unshift(function(value) {
              var valid = isValid(value);
              scope.$parent.subForm.cost.$setValidity('customRequired', valid);
              return valid ? value : undefined;
          });

          ctrl.$formatters.unshift(function(value) {
              scope.$parent.subForm.cost.$setValidity('customRequired', isValid(value));
              return value;
          });

          scope.$watch('$parent.$parent.selectedCostRequired', function() {
              scope.$parent.subForm.cost.$setValidity('customRequired', isValid(ctrl.$modelValue));
          });
      }
  };
});

HTML

<div ng-app="test" ng-controller="MainCtrl">
<form name="form">
    Which grouping is required? <select name="costRequired" ng-model="selectedCostRequired" ng-options="t for t in costRequired"></select>
    <h2>Local</h2>
    <div ng-repeat="fruit in localFruits" ng-form="subForm">
        {{fruit.name}}: <input name="cost" ng-model="fruit.cost" required-when-key="'local'" custom-required/> bound value is: [{{fruit.cost}}]
        <span class="error" ng-show="subForm.cost.$error.customRequired">Required</span>
    </div>
    <h2>Overseas</h2>
    <div ng-repeat="fruit in fruits" ng-form="subForm">
        {{fruit.name}}: <input name="cost" ng-model="fruit.cost" required-when-key="'overseas'" custom-required/> bound value is: [{{fruit.cost}}]
        <span class="error" ng-show="subForm.cost.$error.customRequired">Required</span>
    </div>
    <div ng-show="form.$invalid" class="error">some form error(s) still exist</div>
    <div ng-show="!form.$invalid" class="okay">form looks good!</div>
</form>
</div>
4
  • The fiddle seems to be working, what else do you want it to do? Commented May 17, 2013 at 20:23
  • @sh0ber the problem is the two-way binding is not keeping the original scope up to date. I can see how my question was confusing... I've updated the fiddle to be a little more clear Commented May 17, 2013 at 20:28
  • i'm not exactly sure what's going on in your code, but have you tried using '=attributeToBindTo' instead of '=' in your directive scope? If not, see docs.angularjs.org/guide/directive, scroll to scope under Directive Definition Object Commented May 17, 2013 at 20:34
  • @sh0ber I tried cost: '=ngModel', cost: '=cost' and cost: '=' to no avail. It's not clear to me what I should try to pass in since my input has ng-model="fruit.cost". I assume cost? Commented May 17, 2013 at 20:42

1 Answer 1

2

Using ng-model with a directive that creates an isolate scope on the same element doesn't work.

I suggest either not creating a new scope, or use scope: true.

Here is a simplified example that does not create a new scope:

<form name="form">
    <div ng-repeat="fruit in localFruits">
        {{fruit.name}}: 
        <input ng-model="fruit.cost" required-when-key="local" custom-required/> 
        bound value is: [{{fruit.cost}}]
    </div>
</form>

function MyCtrl($scope) {
    $scope.localFruits = [
      {name: 'banana'},
  ];
}
app.directive('customRequired', function() {
  return {
      restrict: 'A',
      require: 'ngModel',
      link: function(scope, elem, attrs, ctrl) {
          console.log(attrs.requiredWhenKey);
      }
  };
});

fiddle

Since the required-when-key attribute is just a string, you can get the value using attrs.requiredWhenKey.

If you don't create a new scope, you should also be able to remove most of the $parent lookups in your directive.

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

1 Comment

Perfect! I didn't realize the attribute was available to me that way. For others looking at this answer, here is my updated fiddle using Mark's suggestion: jsfiddle.net/cmontgomery/mEvZd

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.