0

Having three input fields

<input type="text" name="foo1" ng-model="data.input1"/>
<ng-messages for="forms.myForm.foo1" role="alert">
    <ng-message when="oneRequired"> Please set foo1 (or foo2 or foo3) </ng-message>
<ng-messages>

<input type="text" name="foo2" ng-model="data.input2"/>
<ng-messages for="forms.myForm.foo2" role="alert">
    <ng-message when="oneRequired"> Please set foo2 (or foo1 or foo3) </ng-message>
<ng-messages>

<input type="text" name="foo3" ng-model="data.input3"/>
<ng-messages for="forms.myForm.foo3" role="alert">
    <ng-message when="oneRequired"> Please set foo3 (or foo1 or foo2) </ng-message>
<ng-messages>

I want to guarantee that at least one input fields value is set. In this case, not only the current validation fields $error should evaluate to 'false' but also all others. All messages should disappear.

My first idea was to use a directive and a unique id to link the fields together:

<input type="text" name="foo1" ng-model="data.input1" one-required="i1_i2_i3_link_identifier/>

Probably I could use a (singleton) service for the registration of the controller and the current values. But I don't have an idea to ensure that all linked controllers (used in the directives) are updated on validation errors.

4
  • i also use directive to display information for some fieds of a form. I pass the form name to the directive so the directive can get the input/error/valid... maybe $scope.$watch in controller or $scope.$apply() in directive can help Commented Nov 9, 2015 at 8:06
  • Can you not check the input in the controller? Commented Nov 9, 2015 at 8:07
  • @OlaviSau: I could but I don't know how to get the other controllers using this service. Commented Nov 9, 2015 at 9:01
  • @JohnRumpel I supplied revised code below with the solution you requested. You can see it is quite busy. I do recommend angular-validator as you can reduce your code and therefor code maintenance greatly. If my solution is what you were looking please mark it as the correct answer. Thanks. Commented Nov 12, 2015 at 17:33

2 Answers 2

1

I highly recommend using the https://github.com/turinggroup/angular-validator directive. It is very flexible and will easily allow to to set your custom validators. I was able to get rid of ng-messages and clean up my html code greatly with this directive.

You can set a custom validator in your controller or service to use throughout your site.

            <input  type = "text"
                    name = "firstName"
                    class = "form-control"
                    ng-model = "form.firstName"
                    validator = "myCustomValidator(form.firstName)"
                    validate-on="dirty"
                    required></div>

Here is a plunker and code for you: http://plnkr.co/edit/X5XdYYekT4YZH6xVBftz?p=preview As you can see this is very clunky and there is a lot of code. With angular validator you can reduce your code to be inline within your inputs and add a controller function.

   <form name="myForm">
      <input type="text" name="foo1" ng-model="data.input1" required/>
      <div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
        <ng-messages for="myForm.foo1" role="alert">
          <ng-message="required"> Please set foo1 (or foo2 or foo3) </ng-message>
        </ng-messages>
      </div>
      <br/>
      <input type="text" name="foo2" ng-model="data.input2" required/>
      <div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
        <ng-messages for="myForm.foo2" role="alert">
          <ng-message="required"> Please set foo2 (or foo2 or foo3) </ng-message>
        </ng-messages>
      </div>
      <br/>
      <input type="text" name="foo3" ng-model="data.input3" required/>
      <div ng-if="myForm.foo1.$error.required && myForm.foo2.$error.required && myForm.foo3.$error.required" class="error">
        <ng-messages for="myForm.foo3" role="alert">
          <ng-message="required"> Please set foo3 (or foo2 or foo3) </ng-message>
        </ng-messages>
      </div>
      <br/>
    </form>  
Sign up to request clarification or add additional context in comments.

1 Comment

That might be an option. But I really want to solve this once using basic angular means
0

I solved the problem using a central Service holding the values and a callback registry. The callbacks are called all the time, when the input changes (using a watcher):

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

    angular.module('myApp').controller('myFormController', function($scope) {
          $scope.data = {
            i1: "remove",
            i2: "all",
            i3: "values"
          };
    });

    angular.module('myApp').factory('oneRequiredService', function() {
          var service = {};
          var container = {};
          var observerCallbacks = {};

          var isValid = function(groupId) {
            var valid = false;

            var modelStates = container[groupId];
            angular.forEach(modelStates, function(modelValid) {
              valid = valid || (modelValid ? true : false);
            });
            return valid;
          };

          var isRegistered = function(groupId) {
            return container.hasOwnProperty(groupId);
          };

          var notifyAll = function(key) {
            var valid = isValid(key);
            if (isRegistered(key)) {
              angular.forEach(observerCallbacks[key], function(callback, index) {
                callback(valid);
              });
            };
          };

          service.register = function(groupId, scopeId, callback) {
            this.updateValue(groupId, scopeId, undefined);
            if (callback) {
              this.registerCallback(groupId, callback);
            }
          };

          service.registerCallback = function(groupId, callback) {
            if (callback) {
              observerCallbacks[groupId] = observerCallbacks[groupId] || [];
              observerCallbacks[groupId].push(callback);
            };
          };

          service.updateValue = function(groupId, scopeId, value) {
            container[groupId] = container[groupId] || {};
            container[groupId][scopeId] = value;
            notifyAll(groupId);
          };

          return service;
        });

        angular.module('myApp').directive('oneRequired', function(oneRequiredService) {
          return {
            restrict: "A",
            require: 'ngModel',
            scope: true,
            link: function(scope, element, attrs, ctrl) {

              var modelAttr = attrs["ngModel"];
              
              var linkIdentifier = attrs["oneRequired"];
              
              var updateCurrentState = function(isValid) {
                scope._valid = isValid;
              };

              scope.$watch(modelAttr, function(newVal, oldVal) {
                oneRequiredService.updateValue(linkIdentifier, scope.$id, newVal);
              });

              scope.$watch('_valid', function(newVal, oldVal) {
                ctrl.$setValidity('oneRequired', newVal);
              });

              oneRequiredService.register(linkIdentifier, scope.$id, updateCurrentState);
            }
          }
        });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>

<body ng-app="myApp">
      <form ng-controller="myFormController" name="forms.myForm">

        <label for="i_1">i_1</label>
        <input id="i_1" name="i1" type="text" one-required="foo-bar" ng-model="data.i1" />
        <span> i1-err-one-required: {{forms.myForm.i1.$error.oneRequired}} </span> <br>
           
        <label for="i_2">i_2</label>
        <input id="i_2" name="i2"  type="text" one-required="foo-bar" ng-model="data.i2"/>
        <span> i2 err-one-required:  {{forms.myForm.i2.$error.oneRequired}} </span> <br>
           
        <label for="i_3">i_3</label>
        <input id="i_3" name="i3" type="text" one-required="foo-bar" ng-model="data.i3"/>
        <span> i3-err-one-required: {{forms.myForm.i3.$error.oneRequired}} </span> <br>
      </form>
    </body>

Comments

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.