user4029082
user4029082

Reputation: 43

Angular directive to set model value to null if option not available any more

I got a edit form with a select drop down.

The options of the select are set as

<select name="Status" 
       ng-model="vm.StatusId" 
       ng-options="x.Id as x.Name for x in Statuses | filterBy: ['Active']: 1" 
      >
        <option value="" >--Choose status --</option>
</select>

now Some statuses can get marked as Active = 0 from time to time, So the user can not select it for a while. The idea is when they load up the form again they have to select an option which is currently active.

In this case angular selects the option --Choose status -- but the model value remains as is.

How do I set the model value vm.StatusId to null in this scenario( when vm.StatusId is not available in ng-options anymore) with out manually checking if that is available, ie I had a custom directive in my mind where I would check in formatters to check if the modelValue is present in the options and then mark the field as in valid. I cant figure out a way to access the available options nicely.

app.directive('test', ['$log',
    function($log) {

        return {
            restrict: 'A',
            priority: 1001,
            terminal: false,
            require: ['ngModel', 'select'],  
            link: function link(scope, elem, attrs, ctrl) {
                var m = ctrl[0],
                    s = ctrl[1];

                m.$formatters.push(function(modelValue) {
                    $log.info('formatters ', modelValue);

                    return modelValue;
                });

                m.$parsers.push(function(viewValue) {
                    $log.info('parsers ', viewValue);
                    return viewValue;
                });
            }
        };
    }
]);

Upvotes: 3

Views: 2706

Answers (1)

Josep
Josep

Reputation: 13071

Well, it's been lots of fun to develop this directive!

What this directive does:

This directive ensures that if the value of the ng-model will be either one of the possible values of the ng-options or null. It works both ways:

  • If one of the values of the select collection is removed and the model was pointing to that value, then it will set the model to null.
  • If an external code tries to set the model to a value that it's not one of the possible options, then the directive will force the model to be null.

That's why its name is modelMatchSelectOrNull.

The Directive:

.directive('modelMatchSelectOrNull',['$parse', function($parse) {
    return {
        restrict: 'A',
        priority: 1001,
        terminal: false,
        require: ['modelMatchSelectOrNull', 'ngModel', 'select'], 
        controller: function(){
            this.possibleValues = [];
        },
        link: function link(scope, elem, attrs, ctrls) {
            var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/;
            var match = attrs['ngOptions'].match(NG_OPTIONS_REGEXP);                
            var valuesFn = $parse(match[7]);
            var valueName = match[4] || match[6];
            var selectAs = / as /.test(match[0]) && match[1];
            var selectAsFn = selectAs ? $parse(selectAs) : null;            
            var keyName = match[5];
            var valueFn = $parse(match[2] ? match[1] : valueName);
            var viewValueFn = selectAsFn ? selectAsFn : valueFn;                
            var locals={};

            var thisController = ctrls[0];   
            var ngModelCtrl = ctrls[1]; 

            function callExpression(exprFn, key, value) {
                locals[valueName] = value;
                if (keyName) locals[keyName] = key;
                return exprFn(scope, locals);
            }

            scope.$watch(attrs['ngModel'], function(newVal, oldVal){
                if(!newVal)
                    return;
                if(thisController.possibleValues.indexOf(newVal)==-1)                    
                    ngModelCtrl.$setViewValue(null);

            });

            scope.$watchCollection(valuesFn, function(newVals, oldVals){                    
                thisController.possibleValues=[];                    
                angular.forEach(newVals,function(value, key){
                    thisController.possibleValues.push(callExpression(viewValueFn, key, value));
                });
                if(thisController.possibleValues.indexOf(ngModelCtrl.$viewValue)==-1)                              
                    ngModelCtrl.$setViewValue(null);
            });
        }        
    };
}]);

And you can use it like this:

<select name="Status" 
       ng-model="vm.StatusId" 
       ng-options="x.Id as x.Name for x in Statuses | filter:{active:true}" 
       model-match-select-or-null>
        <option value="" >--Choose status --</option>
</select>

Working Example

Upvotes: 1

Related Questions