59

Is it possible to use ng-options that it will render into disabled rows based on criteria?

this:

 <select ng-options="c.name group by c.shade for c in colors">

maybe possible to turn into something like this:

 <select ng-options="c.name group by c.shade for c in colors | disabled(c.shade)">

and let's say via a filter that could return disabled='disabled' for all the colors that have shade = "dark"

<select>
   <optgroup label="dark">
      <option value="0" disabled="disabled">black</option>
      <option value="2" disabled="disabled">red</option>
      <option value="3" disabled="disabled">blue</option>
   </optgroup>
   <optgroup label="light">
      <option value="1">white</option>
      <option value="4">yellow</option>
   </optgroup>
 </select>
iLemming
  • 34,477
  • 60
  • 195
  • 309

11 Answers11

69

@lucuma's answer (originally the accepted answer) was correct, but by now should be updated, because this was fixed in Angular 1.4. See the docs of ng-options which also contains an example.

I'm using Angular 1.5 and this works for me:

View

<select ng-model="$ctrl.selectedItem" ng-options="item as item.label disable when item.disabled for item in $ctrl.listItems">

Controller

vm.items = [ { id: 'optionA', label: 'Option A' }, { id: 'optionB', label: 'Option B (recommended)' }, { id: 'optionC', label: 'Option C (Later)', disabled: true } ]; vm.selectedItem = vm.items[1];

Bart
  • 5,065
  • 1
  • 35
  • 43
54

As pointed by @Lod Angular added support for this in 1.4.0-beta.5.

For angular js >= 1.4.0-beta.5.

<select ng-options="c.name disable when c.shade == 'dark' 
group by c.shade for c in colors">

And for angular js < 1.4.0-beta.5 refer the solution below:

Similar to the one given by @lucuma but without jQuery dependency.

Check this http://jsfiddle.net/dZDLg/46/

Controller

<div ng-controller="OptionsController">
    <select ng-model="selectedport" 
        ng-options="p.name as p.name for p in ports"
        options-disabled="p.isinuse for p in ports"></select>
    <input ng-model="selectedport">
</div>

Directive

angular.module('myApp', [])
.directive('optionsDisabled', function($parse) {
    var disableOptions = function(scope, attr, element, data, 
                                  fnDisableIfTrue) {
        // refresh the disabled options in the select element.
        var options = element.find("option");
        for(var pos= 0,index=0;pos<options.length;pos++){
            var elem = angular.element(options[pos]);
            if(elem.val()!=""){
                var locals = {};
                locals[attr] = data[index];
                elem.attr("disabled", fnDisableIfTrue(scope, locals));
                index++;
            }
        }
    };
    return {
        priority: 0,
        require: 'ngModel',
        link: function(scope, iElement, iAttrs, ctrl) {
            // parse expression and build array of disabled options
            var expElements = iAttrs.optionsDisabled.match(
                /^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
            var attrToWatch = expElements[3];
            var fnDisableIfTrue = $parse(expElements[1]);
            scope.$watch(attrToWatch, function(newValue, oldValue) {
                if(newValue)
                    disableOptions(scope, expElements[2], iElement, 
                        newValue, fnDisableIfTrue);
            }, true);
            // handle model updates properly
            scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
                var disOptions = $parse(attrToWatch)(scope);
                if(newValue)
                    disableOptions(scope, expElements[2], iElement, 
                        disOptions, fnDisableIfTrue);
            });
        }
    };
});

Note: This solution doesn't work with group by as rightly pointed by everyone. Refer the solution below by @DHlavaty if you are looking to make it work with group by.

Community
  • 1
  • 1
Vikas Gulati
  • 968
  • 2
  • 10
  • 24
25

Angular added support for this in 1.4.0-beta.5

<select ng-options="c.name disable when c.shade == 'dark' group by c.shade for c in colors">
lod
  • 1,098
  • 10
  • 13
15

I do not believe there is a way to do what you are asking just using ng-options. This issue was raised on the angular project and is still open:

https://github.com/angular/angular.js/issues/638

It seems that the work around is to use a directive which is referenced here and in the github issue: http://jsfiddle.net/alalonde/dZDLg/9/

Here is the entire code from the jsfiddle for reference (the code below is from alande's jsfiddle):

<div ng-controller="OptionsController">
    <select ng-model="selectedport" 
        ng-options="p.name as p.name for p in ports"
        options-disabled="p.isinuse for p in ports"></select>
    <input ng-model="selectedport">
</div>

angular.module('myApp', [])
.directive('optionsDisabled', function($parse) {
    var disableOptions = function(scope, attr, element, data, fnDisableIfTrue) {
        // refresh the disabled options in the select element.
        $("option[value!='?']", element).each(function(i, e) {
            var locals = {};
            locals[attr] = data[i];
            $(this).attr("disabled", fnDisableIfTrue(scope, locals));
        });
    };
    return {
        priority: 0,
        require: 'ngModel',
        link: function(scope, iElement, iAttrs, ctrl) {
            // parse expression and build array of disabled options
            var expElements = iAttrs.optionsDisabled.match(/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
            var attrToWatch = expElements[3];
            var fnDisableIfTrue = $parse(expElements[1]);
            scope.$watch(attrToWatch, function(newValue, oldValue) {
                if(newValue)
                    disableOptions(scope, expElements[2], iElement, newValue, fnDisableIfTrue);
            }, true);
            // handle model updates properly
            scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
                var disOptions = $parse(attrToWatch)(scope);
                if(newValue)
                    disableOptions(scope, expElements[2], iElement, disOptions, fnDisableIfTrue);
            });
        }
    };
});

function OptionsController($scope) {
    $scope.ports = [{name: 'http', isinuse: true},
                    {name: 'test', isinuse: false}];

    $scope.selectedport = 'test';
}
lucuma
  • 18,247
  • 4
  • 66
  • 91
  • Is there a solution without JQuery dependency? – Vikas Gulati Dec 26 '13 at 13:51
  • You can remove jquery and rewrite it using the built in jqlite which is limited. http://docs.angularjs.org/api/angular.element – lucuma Dec 26 '13 at 14:57
  • Can you paste a fiddle with an example of how it could be done? The angular documentation says that find() is limited to lookups by just tag name! – Vikas Gulati Dec 26 '13 at 18:01
  • That's why the answer contains a dependency on jQuery. You might consider giving it a go and posting a separate question when you hit a roadblock. – lucuma Dec 26 '13 at 20:04
  • This works as long as I do not include a blank or not selected option using the method specified in the Angular docs (including inside the select tags). Is there a simple way to get this to work? – Corey Quillen Jan 14 '14 at 22:21
  • 2
    The solution below (without jQuery dependency) works with a blank option, so I went with that. Thanks all! – Corey Quillen Jan 15 '14 at 20:11
  • 2
    UPDATE: the https://github.com/angular/angular.js/issues/638 has now a sollution for this, from february/15. – artdias90 Apr 11 '15 at 10:55
  • @CoreyQuillen I've also encountered the issue with blank option. My fixed version can be found on GitHub https://github.com/ST-Software/STAngular/blob/master/src/directives/SgOptionsDisabled Demo is here http://davidjs.com/2015/05/angular-directive-disable-options/ – David Votrubec May 06 '15 at 13:07
  • 1
    This would now be out of date, since with the more recent versions of angular, to which you should be able to update easily, you have an option to define disabled options by using ng-options directive https://docs.angularjs.org/api/ng/directive/ngOptions – Nemanja Milosavljevic Mar 04 '16 at 12:26
  • This answer was from 2013 and was accurate at the time. Going forward, please review @VikasGulati post. – lucuma Oct 24 '16 at 17:36
8

A similar effect can be achieved using ng-repeat and ng-disabled on the option itself, avoiding the use of a new directive.

HTML

<div ng-controller="ExampleController">
    <select ng-model="myColor">
        <option ng-repeat="c in colors" ng-disabled="c.shade=='dark'" value="{{$index}}">
            {{c.name}}
        </option>
    </select>
</div>

Controller

function ExampleController($scope, $timeout) {
    $scope.colors = [
      {name:'black', shade:'dark'},
      {name:'white', shade:'light'},
      {name:'red', shade:'dark'},
      {name:'blue', shade:'dark'},
      {name:'yellow', shade:'light'}
    ];
    $timeout(function() {
        $scope.myColor = 4; // Yellow
    });
}

Fiddle

http://jsfiddle.net/0p4q3b3s/

Known issues:

  • Does not use ng-options
  • Does not work with group by
  • Selects the index, not the object
  • Requires $timeout for initial selection

Edit : Any object property can be selected (besides the index), but not the object itself. Also, if you have a simple array and not an array of objects, below method will work.

Change this line in HTML :

<option ng-repeat="c in colors" ng-disabled="c.shade=='dark'" value="{{c.name}}">

Change this line in Controller :

$scope.myColor = $scope.colors[4].name; // Yellow
Wasmoo
  • 1,130
  • 9
  • 14
  • could you, please, explain why it requires $timeout? – Sray Mar 07 '15 at 15:00
  • Angular doesn't update the selected when ng-repeat runs, so the selection must happen afterwards. In other words, ng-model first searches its empty list for "myColor == 4", which it can't find, so fills with a blank. Later, ng-repeat runs and sets the list, but the selected element is still blank. $timeout ensures the search for "myColor == 4" happens later. – Wasmoo Mar 09 '15 at 14:53
7

Since February 2015 there has been a way to disable options in your ng-options tag.

This Link shows the addition of the feature on github

I found that using angular 1.4.7, the syntax had changed from 'disable by' to 'disable when'.

The syntax for this is:

'ng-options': 'o.value as o.name disable when o.unavailable for o in options'
dafyddPrys
  • 898
  • 12
  • 23
  • I found that the documentation has no example of disabling when the format is `select as label disable when condition for item in items`. But this answer actually helps. – choz Sep 27 '16 at 04:15
3

I had an interesting situation. An array of dropdowns and I need it to disable the options that were already selected in each of the dropdowns, but I also need it to keep enable the one that was selected already...

here is my plunker: Enable/Disable values with ng-options

var app = angular.module('ngoptions', []);

app.controller('MainCtrl', function($scope) {

  // disable the fields by default
  $scope.coverage = [
    { CoverageType: '', CoverageName: 'No Coverage' },
    { CoverageType: 'A', CoverageName: 'Dependent Only' },
    { CoverageType: 'B', CoverageName: 'Employee Plus Children' },
    { CoverageType: 'C', CoverageName: 'Employee Only' },
    { CoverageType: 'D', CoverageName: 'Employee Plus One' },
    { CoverageType: 'E', CoverageName: 'Employee Plus Two' },
    { CoverageType: 'F', CoverageName: 'Family' },
];
            
            
  // values already set ex: pulled from db          
  $scope.rates = ['A','C', 'F'];     

  $scope.changeSelection = function (index, rate){
     $scope.rates[index] = rate;
     disableRecords();
  }
  
  // start by disabling records
  disableRecords(); 
  
  function disableRecords () {
      // set default values to false
      angular.forEach($scope.coverage, function (i, x) {
             i.disable = false; 
      });
      // set values to true if they exist in the array
      angular.forEach($scope.rates, function (item, idx) {
          angular.forEach($scope.coverage, function (i, x) {
              if (item == i.CoverageType) {
                   i.disable = true; 
              }
          });
      });
  }
  

});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="ngoptions">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <table>
      <thead></thead>
      <tbody>
        <tr ng-repeat="rate in rates">
          <td>
            <select 
            ng-model="rate" 
            ng-change="changeSelection($index, rate)" 
            ng-options="type.CoverageType as type.CoverageName disable when (type.disable == true && type.CoverageType != rate) for type in coverage"></select>
          </td>
        </tr>
      </tbody>
    </table>
  </body>

</html>
Sebastian Castaldi
  • 8,580
  • 3
  • 32
  • 24
3

You can disable using ngOptions in angular 1.4.1 or above

HTML template

<div ng-app="myapp">
<form ng-controller="ctrl">
    <select id="t1" ng-model="curval" ng-options='reportingType.code as reportingType.itemVal disable when reportingType.disable for reportingType in reportingOptions'>
        <option value="">Select Report Type</option>
    </select>

</form>

Controller code

angular.module('myapp',[]).controller("ctrl", function($scope){
$scope.reportingOptions=[{'code':'text','itemVal':'TEXT','disable':false}, {'code':'csv','itemVal':'CSV','disable':true}, {'code':'pdf','itemVal':'PDF','disable':false}];

})

Raja Rathinam
  • 112
  • 1
  • 6
2

Similar "without jQuery" solution as @Vikas-Gulati's, but it works with group by

In my case, group by doesn't work, because my first <option> was without value, just with Please select and item from dropdown text. This is a slightly modified version, that fixes this particular situation:

Usage is simmilar to @Vikas-Gulati answer: https://stackoverflow.com/a/20790905/1268533

Directive

angular.module('disabledModule', [])
    .directive('optionsDisabled', function($parse) {
        var disableOptions = function(scope, attr, element, data, fnDisableIfTrue) {
            var realIndex = 0;
            angular.forEach(element.find("option"), function(value, index){
                var elem = angular.element(value);
                if(elem.val()!="") {
                    var locals = {};
                    locals[attr] = data[realIndex];
                    realIndex++; // this skips data[index] with empty value (IE first <option> with 'Please select from dropdown' item)
                    elem.attr("disabled", fnDisableIfTrue(scope, locals));
                }
            });
        };
        return {
            priority: 0,
            require: 'ngModel',
            link: function(scope, iElement, iAttrs, ctrl) {
                // parse expression and build array of disabled options
                var expElements = iAttrs.optionsDisabled.match(/^\s*(.+)\s+for\s+(.+)\s+in\s+(.+)?\s*/);
                var attrToWatch = expElements[3];
                var fnDisableIfTrue = $parse(expElements[1]);
                scope.$watch(attrToWatch, function(newValue, oldValue) {
                    if(newValue)
                        disableOptions(scope, expElements[2], iElement, newValue, fnDisableIfTrue);
                }, true);
                // handle model updates properly
                scope.$watch(iAttrs.ngModel, function(newValue, oldValue) {
                    var disOptions = $parse(attrToWatch)(scope);
                    if(newValue)
                        disableOptions(scope, expElements[2], iElement, disOptions, fnDisableIfTrue);
                });
            }
        };
    });
Community
  • 1
  • 1
DHlavaty
  • 12,958
  • 4
  • 20
  • 25
1

As I cannot upgrade to latest angularJS, so created a simpler directive to handle it.

.directive('allowDisabledOptions',['$timeout', function($timeout) {
    return function(scope, element, attrs) {
        var ele = element;
        var scopeObj = attrs.allowDisabledOptions;
        $timeout(function(){
            var DS = ele.scope()[scopeObj];
            var options = ele.children();
            for(var i=0;i<DS.length;i++) {
                if(!DS[i].enabled) {
                    options.eq(i).attr('disabled', 'disabled');
                }
            }
        });
    }
}])

for more details: https://github.com/farazshuja/disabled-options

FarazShuja
  • 2,287
  • 2
  • 23
  • 34
  • 1
    just something to add, if the select contains an empty placeholder option, the index of the options and the index of the bound array won't match up. Thus resulting in disabling the previous item in the array. – Richard Bailey Mar 31 '16 at 12:04
1

I also hid disabled options adding fallowing line:

$(this).css("display", fnDisableIfTrue(scope, locals) ? "none" : "block");

It was necessary as I couldn't simply filter them out, as the initial value of this select could be one of the disabled options.

Kamila
  • 371
  • 5
  • 13