当新选项数组不包含值

时间:2017-01-30 22:26:34

标签: angularjs

我有一个基于数组的ngOptions选择。这个数组可以改变。

如果新数组值不包含所选选项值,则selectController将选项值设置为undefined。有办法防止这种情况吗?

Plunker https://plnkr.co/edit/kao3h5ivHXlP1Wrdx1Ib?p=preview

情境:

  • 选择蓝色/红色或绿色
  • 点击缩减为仅有黑白选项
  • 查看模型值是否为空白

通缉行为:模型值保持为蓝色/红色或绿色

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <script src="//code.angularjs.org/snapshot/angular.min.js"></script>
</head>
<body ng-app="selectExample">
  <script>
angular.module('selectExample', [])
  .controller('ExampleController', ['$scope', function($scope) {
    $scope.colorsFull = [
      {id:"bk", name:'black'},
      {id:"w", name:'white'},
      {id:"r", name:'red'},
      {id:"be", name:'blue'},
      {id:"y", name:'yellow'}
    ];
    $scope.colors = $scope.colorsFull;
    $scope.selectedColor =$scope.colorsFull[0];
    $scope.colorsReduced = [
      {id:"bk", name:'black2'},
      {id:"w", name:'white2'}];
  }]);
</script>
<div ng-controller="ExampleController">
  <button ng-click="colors=colorsReduced">Reduced</button>
  <button ng-click="colors=colorsFull">Full</button>
  <br/>
  Colors : {{colors}} 
  <hr/>
  <select ng-model="selectedColor" ng-options="color.name for color in colors track by color.id">
  </select>
  selectedColor:{{selectedColor}}

</div>
</body>
</html>

3 个答案:

答案 0 :(得分:1)

您可以通过跟踪全色下拉列表中选择的颜色并将其插入缩小颜色数组来实现此目的。首先,添加ng-change指令,以便我们可以跟踪所选颜色:

<select ng-model="selectedColor" ng-options="color.name for color in colors track by color.id" ng-change="setColor(selectedColor)">

在你的控制器中:

$scope.setColor = function(color) {
    if(color !== null) {
        // Keep track of the color that is selected
        $scope.previousColor = color;
    }
    else {
        // Changed arrays, keep selected color in model
        $scope.selectedColor = $scope.previousColor;
    }
}  

现在,只要更改了数组,ng-model就会设置为正确的颜色,但是在缩小颜色下拉列表中它会显示为空白,因为该选项不存在。因此,我们需要将该选项插入到数组中。但是,在下拉列表之间来回切换将导致减少颜色的数组继续添加更多选项,我们只想记住我们从全色数组中选择的选项。因此,我们需要创建一组初始颜色,以便在切换时恢复。

// Keep a copy of the original set of reduced colors
$scope.colorsReducedInitial = [
    {id:"bk", name:'black2'},
    {id:"w", name:'white2'}];

最后,我们需要将所选选项插入到缩小颜色数组中。更改缩小按钮上的ng-click以使用功能:

<button ng-click="setColorsReduced()">Reduced</button>

现在,我们可以在将简化颜色数组重置为其初始状态后插入该选项:

$scope.setColorsReduced = function() {
    // Revert back to the initial set of reduced colors
    $scope.colors = angular.copy($scope.colorsReducedInitial);

    if($scope.previousColor !== undefined) {
        var found = false;
        angular.forEach($scope.colorsReducedInitial, function(value, key) {
            if(value.id == $scope.previousColor.id) {
                found = true;
            }
        });

        // If the id is found, no need to push the previousColor
        if(!found) {
            $scope.colors.push($scope.previousColor);
        }
    }
}

请注意,我们正在循环使用缩小颜色数组,以确保我们不会复制任何颜色,例如黑色或白色。

现在,缩小的颜色ng-model具有上一个下拉列表的选定颜色。

Updated Plunkr Demo

答案 1 :(得分:0)

在脚本中使用以下代码

$scope.makeSelected=function(){
    $scope.selectedColor =$scope.colorsReduced[0];
  }

只需在缩小的按钮行中添加此函数调用,如下所示

<button ng-click="colors=colorsReduced;makeSelected()">Reduced</button>

这将实现您想要实现的目标。

答案 2 :(得分:0)

使用Jukebox的答案,我最终编写了一个指令,使用modelCtrl。$ formatters来获取初始值。它还提供了将previousValue存储在范围或局部变量中的可能性:

用法:<select .... select-keep><select .... select-keep="previousColor">

指令:

 .directive('selectKeep', function($parse) {
    return {
      require: 'ngModel',
      link: function (scope, element, attrs, modelCtrl) {
        var previousValueGetter;
        var previousValueSetter;
        if (attrs.selectKeep) { //use a scope attribute to store the previousValue
              previousValueGetter = $parse(attrs.selectKeep);
              previousValueSetter = previousValueGetter.assign;
        }
        else { //use a local variable to store the previousValue
              var previousValue;
              previousValueGetter = function(s) { return previousValue;};
              previousValueSetter = function(s, v) { previousValue = v;};
        }

        //store the initial value
        modelCtrl.$formatters.push(function(v) {
              previousValueSetter(scope, v);
          return v;
        });

        //get notified of model changes (copied from Jukebox's answer)
        modelCtrl.$viewChangeListeners.push(function() {
          if (modelCtrl.$modelValue !== null) {
            previousValueSetter(scope, modelCtrl.$modelValue);
          } else {
            modelCtrl.$setViewValue(previousValueGetter(scope));
          }
        });
      }
    };

Plunker

编辑:它有一个缺陷,即使值没有改变,表单也会变脏。我必须在viewChangeListener的else中添加这些行,但它看起来不太好。有什么想法吗?:

...
} else {
  modelCtrl.$setViewValue(previousValueGetter(scope));
  //set pristine since this change is not a real change
  modelCtrl.$setPristine(true);
  //check if any other modelCtrl is dirty. If not, we will have to put the form as pristine too
  var oneDirty =_.findKey(modelCtrl.$$parentForm, function(otherModelCtrl) {
    return otherModelCtrl && otherModelCtrl.hasOwnProperty('$modelValue') && otherModelCtrl !== modelCtrl && otherModelCtrl.$dirty;
  });
  if (!oneDirty) {
   modelCtrl.$$parentForm.$setPristine(true);
  }
}