Как использовать ng-класс в select с ng-options

У меня есть массив объектов Person

var persons = [
{Name:'John',Eligible:true},
{Name:'Mark',Eligible:true},
{Name:'Sam',Eligible:false},
{Name:'Edward',Eligible:false},
{Name:'Michael',Eligible:true}
];

и я использую select с ng-опциями следующим образом:

<select ng-model="Blah" ng-options="person.Name for person in persons"></select>

Я хочу показать запись с Eligible:false красным цветом. Итак, проблема в том, как мне использовать ng-class в select, чтобы добиться этого? Поскольку мы не используем тег option, это не сработает, если я просто добавлю ng-class в сам элемент select.


person I_Debug_Everything    schedule 07.03.2013    source источник
comment
используйте директиву для перебора параметров и применения класса к тегам параметров, которые соответствуют условию   -  person charlietfl    schedule 07.03.2013
comment
да, я могу это сделать, но нет ли способа сделать это напрямую? Я имею в виду, что здесь должен быть способ использовать ng-класс.   -  person I_Debug_Everything    schedule 07.03.2013
comment
К вашему сведению: я обновил свой ответ, потому что в нем было несколько ошибок. Не уверен, что это повлияет на вашу текущую кодовую базу, но если вы используете то, что я опубликовал раньше, вы можете взглянуть.   -  person Ben Lesh    schedule 30.11.2013


Ответы (9)


Вы можете создать директиву, которая обрабатывала параметры после обработки директивы ngOptions, которая обновляла их соответствующими классами.

Обновление: в старом коде было несколько ошибок, и я кое-что узнал после того, как ответил на этот вопрос. Это планк, который был переделан в 1.2.2 (но должен работать и в 1.0.X)

Вот обновленный (30 нояб. 2013, 03:17) код:

app.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);
            }
          });
        });
      });
    }
  };
});

Вот как вы могли бы использовать его в своей разметке:

<select ng-model="foo" ng-options="x.name for x in items" 
        options-class="{ 'is-eligible' : eligible, 'not-eligible': !eligible }">
</select>

Он работает так же, как ng-class, за исключением того, что он работает для каждого элемента в коллекции.

person Ben Lesh    schedule 07.03.2013
comment
Это отличное решение. Всегда есть директива, чтобы спасти положение!! :D +1 - person I_Debug_Everything; 08.03.2013
comment
Было весело разбираться. Мне понравилось расставаться, когда я создал новую область видимости и расширил ее с помощью ребенка, которого я осматривал, чтобы я мог оценить его! Такой угловатый, очень угловатый. Заставляет меня издавать лазерные шумы, пока я программирую... пиу-пиу-пиу. - person Ben Lesh; 08.03.2013
comment
блин, твоя директива НЕ работает корректно. Классы, применяемые до выбора любого «варианта» и после него, противоположны; правильные значения применяются только после выбора одного из доступных элементов ‹option›... - person Kulbi; 29.11.2013
comment
@CloudRide, перестань орать. :P ... какая версия Angular? - person Ben Lesh; 30.11.2013
comment
@CloudRide, спасибо, что обратили на это мое внимание. Я обновляю ответ тем, что работает более последовательно. Также это сделано в 1.2.2 (но, я думаю, должно работать в любой версии к северу от 1.0.7) - person Ben Lesh; 30.11.2013
comment
@FlorenceFoo определенно, но вам нужно будет выбрать теги параметров и прокрутить их, проверяя значение для каждого, например так: jsbin.com/jejim/1/edit?html,css,js,output - person Ben Lesh; 28.07.2014
comment
Для чего нужен ngSelect в списке параметров ссылки? Я не мог найти никакой информации в документации - person Jay Jay Jay; 24.03.2015
comment
В моем случае список опций изменяется после вызова функции ссылки, потому что я извлекаю массив опций через $resource. Чтобы гарантировать, что обработчик $watch всегда вызывается после заполнения массива опций, мне пришлось добавить true в качестве третьего аргумента к scope.watch() в вашей директиве. - person Jannik Jochem; 12.05.2015
comment
На самом деле этого было недостаточно, чтобы заставить директиву вести себя корректно. Вот работающий плункер: plnkr.co/edit/duiAehJNdoBsYqnst5ad. Мне пришлось использовать scope.$watchCollection, а также scope.$$postDigest, чтобы гарантировать, что классы добавляются после того, как ng-options уже обновил элементы options. - person Jannik Jochem; 12.05.2015
comment
Хорошее исправление! Мне пришлось добавить следующее, чтобы убрать фильтрацию, которая была у меня внутри атрибута ng-options: // get the source for the items array that populates the select. var optionsFiltersStripped = (attrs.ngOptions.indexOf('|') > -1) ? attrs.ngOptions.split('|')[0] : attrs.ngOptions; var optionsSourceStr = optionsFiltersStripped.trim().split(' ').pop(), // use $parse to get a function from the options-class attribute // that you can use to evaluate later. getOptionsClass = $parse(attrs.optionsClass); - person Ben Barreth; 10.07.2015
comment
Этот код не работает с angularjs 1.4 и выше, но отлично работает в предыдущих версиях. - person Jared; 19.08.2015
comment
Это не работает в версии 1.4.7. Любое предложение, что нужно изменить, чтобы заставить его работать в 1.4.7? Я попробовал плункер и перешел на 1.4.7, и он подтвердил, что он не работает. - person Strategist; 23.10.2015
comment
Значение, заданное по умолчанию для параметра, изменено в angular 1.4.x. До версии 1.4.x это значение основывалось на индексе. Посмотрите код выше, как индекс, взятый в forEach, используется для установки значения параметров. После версии 1.4.x значением по умолчанию является item.$hashKey, что-то вроде object:1234. Измените строку elem.find('option[value=' + index + ']'); to elem.find('option[value=' + item.$hashKey + ']'); и это будет рабочий вариант. Но подумайте о том, чтобы использовать отслеживание, чтобы дать параметру известное значение. - person yeraycaballero; 14.03.2016
comment
Ничто из этого больше не работает в 1.5.8, даже приведенная выше настройка @yeraycaballero. - person Grandizer; 13.10.2016
comment
@BenLesh Ваше решение все еще работает на Angular 1.6 с несколькими изменениями: plnkr.co/edit/NpJUjYOUIYs5Yso7A1eo? p=preview Angular теперь ссылается на элементы по их $$hashkey, а не по индексу, если не используется track by. В противном случае он будет использовать любую дорожку по свойству, которое вы используете. Мое решение грязное, но работает для всех, кому нужна отправная точка! - person Coo; 04.01.2017

В этом сценарии вы можете применить ng-класс только в том случае, если вы используете ng-repeat с тегами параметров:

<select ng-model="Blah">
  <option ng-repeat="person in persons" ng-class="{red: person.Eligible}">
    {{person.Name}}
  </option>  
</select>

Это даст индивидуальный класс вашим «подходящим» людям, но CSS не будет работать последовательно для разных баузеров.

Плункер.

person Stewie    schedule 07.03.2013
comment
это работает, но я хочу использовать с ng-options, так как это не позволит выбрать какое-либо значение, как только я сохраню его в своей базе данных и обновлю. Вы не знаете, как сделать так, чтобы моя запись оставалась выбранной? я уже использовал ng-selected для этого, но это тоже не сработает - person I_Debug_Everything; 07.03.2013
comment
Это не проблема. Я прикрепил ссылку Plunker к ответу. - person Stewie; 07.03.2013
comment
Спасибо чувак!! я использовал то же самое. Единственная разница в том, что я использовал jquery версии -1.7 и, следовательно, не поддерживал ng-selected. Изменил ссылку на версию, и это творит чудеса!! - person I_Debug_Everything; 07.03.2013
comment
извините, что сняли пометку с вашего ответа как правильный ответ. Ваше решение работает нормально, но оно по-прежнему не решает основную проблему моего вопроса, то есть использование ng-класса с выбором, имеющим ng-опции. Пожалуйста, не возражайте - person I_Debug_Everything; 07.03.2013
comment
Это не проблема, но в любом случае вы не сможете указать Angular использовать ng-class с ng-options, чтобы применять пользовательские классы к сгенерированным тегам параметров. - person Stewie; 07.03.2013
comment
Можно использовать с несколькими атрибутами как показано здесь - person The Red Pea; 10.03.2016

Я хотел прокомментировать принятый ответ, но, поскольку у меня недостаточно очков репутации, я должен добавить ответ. Я знаю, что это старый вопрос, но комментарии недавно добавлены к принятому ответу.

Для angularjs 1.4.x предлагаемая директива должна быть адаптирована, чтобы она снова заработала. Из-за критического изменения в ngOptions значение параметра больше не является индексом, поэтому строка

option = elem.find('option[value=' + index + ']');

больше не будет работать.

Если вы измените код в плункере на

<select ng-model="foo" ng-options="x.id as x.name for x in items" 
        options-class="{ 'is-eligible' : eligible, 'not-eligible': !eligible }">
</select>

В результате значение тега опции теперь будет

value="number:x" (x — идентификатор объекта элемента)

Измените директиву на

option = elem.find('option[value=\'number:' + item.id + '\']');

чтобы он снова работал.

Конечно, это не общее решение, потому что что, если у вас нет идентификатора в вашем объекте? Затем вы найдете value="object:y" в своем теге option, где y — это число, сгенерированное angularjs, но с этим y вы не можете сопоставить свои элементы.

Надеется, что это поможет некоторым людям снова заставить свой код работать после обновления angularjs до 1.4.x.

Я также пытался использовать track by в ng-options, но это не сработало. Может быть, у людей больше опыта в angularjs, чем у меня (= мой первый проект в angularjs)?

person evb    schedule 11.12.2015

Директива односторонняя, но я использовал пользовательский фильтр. Если вы знаете, как выбрать свой элемент, у вас все должно получиться. Задача состояла в том, чтобы найти текущий элемент option внутри select. Я мог бы использовать селектор «содержит», но текст в параметрах может быть не уникальным для элементов. Чтобы найти параметр по значению, я внедрил область действия и сам элемент.

<select ng-model="foo" ng-options="item.name|addClass:{eligible:item.eligible,className:'eligible',scope:this,item:item} for item in items"></select>

и в js:

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

app.filter('addClass', function() {
  return function(text, opt) {
    var i;
    $.each(opt.scope.items,function(index,item) {
      if (item.id === opt.item.id) {
        i = index;
        return false;
      }
    });
    var elem = angular.element("select > option[value='" + i + "']");
    var classTail = opt.className;
    if (opt.eligible) {
      elem.addClass('is-' + classTail);
      elem.removeClass('not-' + classTail);
    } else {
      elem.addClass('not-' + classTail);
      elem.removeClass('is-' + classTail);
    }
    return text;
  }
})

app.controller('MainCtrl', function($scope) {
  $scope.items = [
    { name: 'foo',id: 'x1',eligible: true},
    { name: 'bar',id: 'x2',eligible: false}, 
    { name: 'test',id: 'x3',eligible: true}
  ];
 });

Здесь вы можете увидеть, как это работает.

person westor    schedule 22.11.2013
comment
На самом деле просто покончите с отправкой коллекции элементов в фильтр (через объект вашей области). Используйте такой индекс ‹выберите ng-options=item.name|addClass:{ix:idx,...} для (idx,item) в элементах› В коде фильтра вы можете удалить цикл $.each, который может быть расточительно, если ваша коллекция большая. Ваша находка elem теперь такая -- var elem = angular.element(select › option[value=' + opt.ix + ']); Спасибо за ваш ответ, в итоге я использовал его со своим модом. - person Craig; 25.11.2014
comment
Да, вы правы, спасибо за совет. Когда я сделал это предложение, я не знал, насколько дорогими являются фильтры в целом, и что внедрение области действия через параметр не является частью передовой практики. - person westor; 25.11.2014
comment
Я бы не стал использовать фильтр для этого. Как правило, фильтры также должны использоваться в службах и контроллерах. Выполнение манипуляций с DOM нарушает это. Директивы предназначены для этого. - person Dormouse; 15.12.2014

Принятый ответ у меня не сработал, поэтому я нашел альтернативу без пользовательской директивы, используя track by :

<select ng-model="foo" ng-options="x.name for x in items track by x.eligible"></select>

Каждый параметр теперь получает значение x.eligible. В CSS вы можете стилизовать параметры со значением = true (я думаю, что true должна быть строкой). CSS:

option[value="true"]{
    color: red;
}
person chriscross    schedule 23.09.2014
comment
Не вызовет ли это ошибку дублирования элемента, потому что подходящие элементы должны быть уникальными? Таким образом, это не будет работать с данными, кроме логических (да/нет выбора). - person Dormouse; 15.12.2014
comment
Хорошее простое решение для некоторых типов данных. - person Chris Smith; 23.01.2017

Если вы хотите не только показать их красным цветом, но и запретить пользователю выбирать параметры, вы можете использовать disable when:

<select 
    ng-model="Blah"
    ng-options="person.Name disable when !person.Eligible for person in persons">
</select>

Затем вы можете использовать CSS, чтобы установить цвет отключенных опций.

person lex82    schedule 12.04.2016

Я не могу написать это как комментарий из-за репутации, но я обновил плункер для принятого ответа для работы с Angular 1.4.8. Спасибо Бену Лешу за оригинальный ответ, он мне очень помог. Разница, похоже, в том, что более новый Angular генерирует такие параметры:

<option class="is-eligible" label="foo" value="object:1">foo</option>

так что код

option = elem.find('option[value=' + index + ']');

не смог бы найти вариант. Мое изменение анализирует ngOptions и определяет, какое поле элемента использовалось для метки, и находит параметр на основе этого вместо значения. Видеть:

http://plnkr.co/edit/MMZfuNZyouaNGulfJn41

person Shelby Robertson    schedule 04.02.2016

Я знаю, что немного опоздал на вечеринку, но для людей, которые хотят решить эту проблему с помощью чистого CSS, без использования директивы вы можете создать такой класс css:

select.blueSelect option[value="false"]{
    color:#01aac7;
}

Это правило css гласит: найти все элементы со значением = false с именем тега «option» внутри каждого «выбора», который имеет класс «blueSelect», и сделать цвет текста # 01aac7; (оттенок синего)

В вашем случае ваш HTML будет выглядеть так:

<select class="form-control blueSelect" name="persons" id="persons1"
        ng-options="person as person.name for person in $ctrl.persons track by person.Eligible"
        ng-model="$ctrl.selectedPerson" required>
    <option disabled selected value="">Default value</option>
</select>

Отслеживание внутри ng-options — это то, что будет содержать то, по чему следует отслеживать параметры, или поле «значение» каждого параметра. Обратите внимание, что в зависимости от потребностей вашего проекта вам, возможно, придется внести некоторые изменения, чтобы все работало в соответствии с вашими требованиями.

Но это не будет работать правильно, когда есть несколько вариантов с одинаковым значением для допустимого поля. Итак, чтобы это работало, мы создаем составное выражение для отслеживания, таким образом, мы можем иметь уникальные значения для отслеживания в каждой опции. В этом случае мы объединяем оба поля Name и Eligible.

Итак, теперь наш html будет выглядеть так

<select class="form-control blueSelect" name="persons" id="persons2"
        ng-options="person as person.name for person in $ctrl.persons track by (person.name + person.Eligible)"
        ng-model="$ctrl.selectedPerson" required>
    <option disabled selected value="">Default value</option>
</select>

и наш css:

select.blueSelect option[value*="False"]{
    color:#01aac7;
}

Обратите внимание на * рядом со значением, это регулярное выражение, которое означает найти слово «False» где-то в поле значения элемента option.

Быстрое редактирование. Вы также можете отключить параметры с Eligible = False, используя «отключить, когда» в выражении ng-options, например:

ярлык отключить, когда отключить для значения в < em>массив отслеживать по выражению_отслеживания

Я оставлю, как использовать это в вашем случае, чтобы вы узнали ;-)

Это работает для простых модификаций css, для более сложных вещей вам может понадобиться директива или другие методы. Проверено в хроме.

Я надеюсь, что это поможет кому-то там. :-)

person RGonzalez    schedule 24.05.2016

Я нашел другой обходной путь, который проще, чем добавление директивы или фильтра, а именно добавление обработчика события onfocus, применяющего стиль.

angular.element('select.styled').focus( function() {
  angular.element(this).find('option').addClass('myStyle');
});
person Rob    schedule 09.04.2018