Search code examples
javascriptangularjsangularjs-directiverequired

Angular: how to bind to required/ngRequired


I have this directive which can be required or not. It can be used in two ways (as far as I know)

<my-foo required></my-foo>

or

<my-foo ng-required="data.value > 10"></my-foo>

So, because require and ngRequire are basically the same thing you would think that the directive could do this

HTML:

<my-foo ng-require="data.isRequired"></my-foo>

JS:

...
.directive('myFoo', function () {
    return {
    restrict: 'E',
    scope: {
       required: '='
    }
    ...

DEMO

Well, nope, this doesn't work, scope.require is undefined. You actually have to change the scope definition to

scope: {
    required: '=ngRequired'
}

So the question is what is the preferred way to handle both situation such that the value gets stored in scope.required ? Should I defined both or use attrs from the link function ?


Solution

  • There are basically 2 approaches you can pick:

    1. Custom form element supporting ng-model

    If you peek at the ng-required directive source code you'll find it only deals with ng-model controller:

    restrict: 'A',
    require: '?ngModel',
    link: function(scope, elm, attr, ctrl) {
      if (!ctrl) return;
      attr.required = true; // force truthy in case we are on non input element
    
      ctrl.$validators.required = function(modelValue, viewValue) {
        return !attr.required || !ctrl.$isEmpty(viewValue);
      };
    
      attr.$observe('required', function() {
        ctrl.$validate();
      });
    }
    

    Thus if you custom directive supports ng-model you already have support for ng-required i.e.:

    angular.module('test', [])
    .directive('myInput', function(){
      return {
        restrict: 'E',
        require: 'ngModel',
        scope: true,
        template: '<div><button ng-click="changeValue()">Change Value from: {{currentValue}}</button></div>',
        link: function (scope, element, attrs, ngModelCtrl) {
            ngModelCtrl.$parsers.push(function(val){
              if(!val){
                return null;
              }
              return parseFloat(val, 10) * 100;
            });
            ngModelCtrl.$render = function() {
               scope.currentValue = ngModelCtrl.$viewValue || 'No value';
            };
            scope.changeValue = function read(){
              var newValue = Math.random();
              if(newValue > 0.5){
                ngModelCtrl.$setViewValue(newValue + "");
              } else {
                ngModelCtrl.$setViewValue(null);
              }
              ngModelCtrl.$render();
            };
        }
      };
    });

    2. Wrap existing directive and pass ng-required:

    angular.module('test', [])
      .directive('myFormElement', function() {
          return {
            restrict: 'E',
            scope: {
              model: '=',
              required: '='
            },
            template: '<div>Enter number: <input type="number" ng-model="data.number" ng-required="required"></div>'
      };
    
      });
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
    
    <div ng-app="test" ng-init="data={value:'Initial', required: false}">
      <form>
        Is required: <input type="checkbox" ng-model="data.required">
        <my-form-element required="data.required" model="data"></my-form-element>
      </form>
    </div>