ng-options with disabled rows
Asked Answered
S

11

60

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>
Savoirfaire answered 24/4, 2013 at 21:9 Comment(0)
R
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];

Rigi answered 1/11, 2016 at 7:31 Comment(0)
D
55

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.

Dibbuk answered 26/12, 2013 at 20:34 Comment(2)
This should be the accepted answer. The accepted answer did not work for me :(Choler
If you are minifying, be sure to use DI for $parse: .directive('optionsDisabled', ['$parse' , function ($parse) { ... }]);Ostium
Q
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">
Quinquevalent answered 8/4, 2015 at 8:23 Comment(1)
Killed it. Thanks! ng-options="item as item.label disable when item.disabled for item in items track by item.id" required>.Ralleigh
H
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';
}
Hoo answered 24/4, 2013 at 22:49 Comment(10)
Is there a solution without JQuery dependency?Dibbuk
You can remove jquery and rewrite it using the built in jqlite which is limited. docs.angularjs.org/api/angular.elementHoo
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!Dibbuk
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.Hoo
This works as long as I do not include a blank or not selected option using the method specified in the Angular docs (including <option value=""></option> inside the select tags). Is there a simple way to get this to work?Stout
The solution below (without jQuery dependency) works with a blank option, so I went with that. Thanks all!Stout
UPDATE: the github.com/angular/angular.js/issues/638 has now a sollution for this, from february/15.Kalle
@CoreyQuillen I've also encountered the issue with blank option. My fixed version can be found on GitHub github.com/ST-Software/STAngular/blob/master/src/directives/… Demo is here davidjs.com/2015/05/angular-directive-disable-optionsSuiter
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 docs.angularjs.org/api/ng/directive/ngOptionsNummary
This answer was from 2013 and was accurate at the time. Going forward, please review @VikasGulati post.Hoo
C
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
Calvin answered 26/9, 2014 at 15:4 Comment(2)
could you, please, explain why it requires $timeout?Sse
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.Calvin
F
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'
Fortyish answered 23/11, 2015 at 16:7 Comment(1)
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.Selfseeking
K
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="[email protected]" 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>
Kuibyshev answered 4/11, 2015 at 20:24 Comment(0)
A
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}];

})

Anemophilous answered 20/4, 2016 at 8:12 Comment(0)
L
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://mcmap.net/q/325310/-ng-options-with-disabled-rows

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);
                });
            }
        };
    });
Lientery answered 8/12, 2014 at 15:21 Comment(0)
L
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

Lejeune answered 30/9, 2015 at 11:1 Comment(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.Selfsupport
F
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.

Foghorn answered 22/10, 2015 at 10:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.