Search code examples
angularjstemplatesupdatesui-select2

Angular Select2 formatResult not updating the template


I have a select2 drop down with the following markup:

<select id="selectByName" ui-select2="select2Options" ng-model="selectId" data-placeholder="Select item by name" style="width:250px">
  <option></option>
  <option ng-repeat='item in items' data-show="{{item.show}}" value="{{item.id}}">
    {{item.name}}
  </option>       
</select>

And the js contains the following:

$scope.items (an array that has a id, a boolean show property and a name property)

and the select 2 options:

select2Options : {
       allowClear: true,
       placeholder:"select a value",
       formatResult: function(state) {
          var $elem = angular.element(state.element),
          isVisible = $elem.data('show');
          return isVisible ? '<span style="color:red">'+state.text+'</span>':
                              <span style="color:blue">'+state.text+'</span>';

       }
 },

Well, the ng-repeat updates correctly the html markup and sets data-show attribute to either true or false, but the formatResult function does not update this value. In the html source the data-show="true" and in the formatResult function $elem.data('show') = false;, why doesn't it update while the function is called every time the select2 is opened?

Here is made a plunker that illustrates my question: plnkr.co/edit/d0LxuhzdQh7hMdzOoxpr?p=preview .It looks the formatResult updates the results correctly only once before opening the select2 for the first time.


Solution

  • Edit

    http://plnkr.co/edit/6Vma1WTQWQw0HAIQUVxE?p=preview

      $scope.select2options = {
        allowClear: true,
        placeholder: "select a value",
        formatResult: function(state, container) {
          var $elem = angular.element(state.element);
          var scope = $elem.data('$scope');
          if (scope !== undefined) {
            isVisible = scope.$eval($elem.data('show'));
            $scope.dataShow[$elem.attr('value')] = isVisible;
            $scope.updated++;
            return isVisible ? '<span style="color:red">' + state.text + '</span>' :
              ' <span style="color:blue">' + state.text + '</span>'
          }
        }
      }
    

    The key part is grabbing the $scope data from the jqLite element and then calling $eval, which evaluates an unparsed string expression in the context of the scope. If we had used $scope.$eval, it would have used the controller $scope, which wouldn't have the ng-repeat on it. By grabbing it from the element we have a scope that has access to the item property for the ng-repeat.

    Having said that I don't recommend using this code (sometimes jQuery widgets force you into unpleasant corners when working with angular). Again if you find yourself manipulating angular.element or using $element in a controller you probably should use a directive instead. Then again we programmers have to deal with non-ideal constraints (time, money, etc.) that prevent us from working "ideally" so given your context this may be a decent solution.

    Let me know if any of my explanation doesn't make sense.

    Original

    http://plnkr.co/edit/vYTdxPwgwqZSgK5m9yk9?p=preview

    Is this what you want?

    JavaScript

      $scope.items = [{
        id: 1,
        show: false,
        name: 'test1'
      }, {
        id: 2,
        show: true,
        name: 'test2'
      }, {
        id: 3,
        show: true,
        name: 'test3'
      }];
    
      $scope.selections = [1, 2];
    
      $scope.getStyleForIndex = function (index) {
        var item;
        for (var i = 0; i < $scope.items.length; i++) {
          if (i === index) {
            item = $scope.items[i];
            break;
          }
        }
    
        return item.show ? { color: "red" } : { color: "blue" };
      }
    
      $scope.select2options = {
        allowClear: true,
        formatResult: function(item, container) {
          var color = $scope.getStyleForIndex(parseInt(item.id, 10)).color;
          container.html('<span style="color:' + color  + '">RESULT ' + item.text + '</span>');
        },
        formatSelection: function(item, container) {
          container.append($compile('<span ng-style="getStyleForIndex(' + item.id + ')">SELECTION ' + item.text + '</span>')($scope));
        }
      }
    

    HTML

      <div ng-repeat="item in items">
        {{ item.name }}  
        <input type="checkbox" ng-model="item.show" />
      </div>
    
      <select ui-select2="select2options" ng-model="selections" style="width:200px" multiple="true" ng-options="i.id as i.name for i in items"></select>
      {{selections}}