0

I've been struggling with a problem for a few days. I'm trying to apply a class to an option tag based on a value in the model it is bound to. I tried using ng-Class as this post (How to use ng-class in select with ng-options) describes, but that didn't work. So then I tried using the directive from that post and that didn't work either. My problem seems to be in the expression as it is either always true or always false and never based on the value in the model. I'm not sure if it's something to do with how the expression is being handled by $parse. Here is the view:

<div class="col-md-2">
    <div class="merchant-list">
        <input type="checkbox"
            ng-model="allMerchants"
            ng-change="allMerchants_Changed()">All merchants<br />
        <select size="10"
            ng-model="overviewCtrl.currentMerchant"
            ng-options="item.Id as item.Name for item in allMerchantData"
            ng-disabled="allMerchants"
            ng-change="currentMerchant_Changed()"
            options-class="{ 'merchant-item-waiting':item.status=='w','merchant-item-error':item.status=='e','merchant-item-loaded':item.status=='l'}"
            >
            <option value="">--All Merchants--</option>
        </select>
        <p>Current Merchant: [{{ overviewCtrl.currentMerchant }}]</p>
    </div>
</div>

Here is how I am setting the status on the model when the data is returned.

MerchantService.getAllMerchantData().query(function (response) {
        // Add a status flag to the merchant.  waiting, loaded, error
        for (var merchant in response)
        {
            response[merchant].status = 'w';
        }

        $scope.allMerchantData = response;
        SystemMetricService.loadSystemMetrics(response, $scope);
    }, function (error) {
        SharedService.logError("Error getting all merchant data", error);
    });

For reference, here is the directive so you don't have to go to How to use ng-class in select with ng-options:

angular.module('app.directives')
.directive('optionsClass', function ($parse) {
    return {
        require: 'select',
        link: function (scope, elem, attrs, ngSelect) {
            // get the source for the items array that populates the select.
            var optionsSourceStr = attrs.ngOptions.split(' ').pop(),
            // use $parse to get a function from the options-class attribute
            // that you can use to evaluate later.
                getOptionsClass = $parse(attrs.optionsClass);

            scope.$watch(optionsSourceStr, function (items) {
                // when the options source changes loop through its items.
                angular.forEach(items, function (item, index) {
                    // evaluate against the item to get a mapping object for
                    // for your classes.
                    var classes = getOptionsClass(item),
                    // also get the option you're going to need. This can be found
                    // by looking for the option with the appropriate index in the
                    // value attribute.
                        option = elem.find('option[value=' + index + ']');

                    // now loop through the key/value pairs in the mapping object
                    // and apply the classes that evaluated to be truthy.
                    angular.forEach(classes, function (add, className) {
                        if (add) {
                            angular.element(option).addClass(className);
                        }
                    });
                });
            });
        }
    };
});
Community
  • 1
  • 1
Randar Puust
  • 363
  • 4
  • 14

1 Answers1

1

As you suspect, it has to do with $parse.

You write

options-class="{ 'merchant-item-waiting':item.status=='w','merchant-item-error':item.status=='e','merchant-item-loaded':item.status=='l'

Notice how you reference item.status explicitly. Following the original example, you should just write status instead.

Now, why is this? Looking at the documentation for $parse (https://docs.angularjs.org/api/ng/service/$parse) we see this part:

Returns function(context, locals) - a function which represents the compiled expression:

context – {object} – an object against which any expressions embedded in the strings are evaluated against (typically a scope object).

In the directive we have the part:

var classes = getOptionsClass(item),

which means in this case, you can think of item (one of the options) as being scope-like, in the sense that you wouldn't write scope.status in the code, but just reference status.

SveinT
  • 703
  • 5
  • 10
  • Thank you SveinT! That was it exactly. Thank you for finding the issue and explaining it in detail. I can feel my hair getting less gray now. – Randar Puust Apr 25 '14 at 21:02