Angular.js 1.4.7 dynamic ng-options causing mutipl

2019-09-09 00:15发布

问题:

I have a select list where the content is dynamically generated from the content selected in the first select list. I cant seem to find why I'm getting these errors fired every time the first select list is changed:

Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn:
regularInterceptedExpression","newVal":42,"oldVal":37},{"msg":"fn:       regularInterceptedExpression","newVal":"16","oldVal":"14"}],[{"msg":"fn: regularInterceptedExpression","newVal":47,"oldVal":42},{"msg":"fn: regularInterceptedExpression","newVal":"18","oldVal":"16"}],[{"msg":"fn: regularInterceptedExpression","newVal":52,"oldVal":47},{"msg":"fn: regularInterceptedExpression","newVal":"20","oldVal":"18"}],[{"msg":"fn: regularInterceptedExpression","newVal":57,"oldVal":52},{"msg":"fn: regularInterceptedExpression","newVal":"22","oldVal":"20"}],[{"msg":"fn: regularInterceptedExpression","newVal":62,"oldVal":57},{"msg":"fn: regularInterceptedExpression","newVal":"24","oldVal":"22"}]]
http://errors.angularjs.org/1.4.7/$rootScope/infdig?p0=10&p1=%5B%5B%7B%22ms…Expression%22%2C%22newVal%22%3A%2224%22%2C%22oldVal%22%3A%2222%22%7D%5D%5D

Plus each time the list is changed, the "makeOptions" used to generate the second list is fired several times per single change of the list.

The two lists are setup like so:

    <select ng-model="selected.level1" ng-options="lvl.name as lvl.name for lvl in level1options"></select>
    <select ng-model="selected.level2" ng-options="opt.value as opt.label for opt in makeOptions()"></select>

and the controller is:

app.controller('DemoController', function($scope) {

  $scope.level1options = [{ name: "A", value: "A" }, { name: "B", value: "B" }, { name: "C", value: "C" }];
  $scope.makeOptionsCallCount = 1;
  $scope.selected = {};


  $scope.makeOptions = function() {

    console.log($scope.makeOptionsCallCount++);

    var options = [];

    for (var i = 0; i < 5; i++) {
        options.push({ label: 'Value = ' + i + $scope.selected.level1, value: i + $scope.selected.level1 });
    }

    return options;
  };

});

Here is an example of what is going weird:

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

Any ideas where I've gone wrong?

UPDATE

I may have over simplified the example where I was having troubles. The solution becomes more complex because I have a list of items where for each item you can choose a set of values. I apologize, I overlooked this element when asking my original question.

I have created new example that more accurately reflects this:

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

回答1:

When you use function inside view, it execute on every digest loop.

Also angular add watcher to result this function, and if you, for example, return new array on every call - you go to infinit loop, because [] != []

In your case, you have also another error: inside function that called in view you change variable, that also showed in view. So for this variable also added watcher.

You change value in function call, watch see that variable changed, run digest again, run function, change value, see that variable changed and etc...

So, for solution, better simple save options on change first select.

var app = angular.module('demoApp', [])
    
    app.controller('DemoController', function($scope) {
      
      $scope.level1options = [{ name: "A", value: "A" }, { name: "B", value: "B" }, { name: "C", value: "C" }];
      $scope.makeOptionsCallCount = 1;
      $scope.selected = {};
      
      
      $scope.makeOptions = function(lvl) {
        
        $scope.options = []; 
        console.log(++$scope.makeOptionsCallCount);

        for (var i = 0; i < 5; i++) {
            $scope.options.push({ label: 'Value = ' + i + lvl, value: i + lvl });
        }
        
      };

    });
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.js"></script>
    <div ng-app="demoApp" ng-controller="DemoController">
      <div>
        
        <h2>Infinite Digest 2</h2>
        
        <select ng-model="selected.level1" ng-options="lvl.name as lvl.name for lvl in level1options" ng-change="makeOptionsCallCount=0;makeOptions(selected.level1)"></select>
        
        <select ng-model="selected.level2" ng-options="opt.value as opt.label for opt in options"></select>

        <br/>
        <br/>
        <div>Level 1: {{selected.level1}}</div>
        <div>Level 2: {{selected.level2}}</div>
        <div>makeOptions() Called: {{makeOptionsCallCount}} times</div>
      </div>
    </div>

UPDATE:
In your case from comment, you can take advantage of the fact that ng-repeat create own scope, and save to it list options, created on ng-change event.

also, you can even use your function makeOptions.

To ng-change you can pass expression:

ng-change="optionsLvLs = makeOptions(option.level1)"

here in ng-repeat scope for every option, on change event would be created optionsLvLs that not pollute your option object, if this was matter.

    var app = angular.module('demoApp', [])
    
    app.controller('DemoController', function($scope) {
      
      $scope.level1options = [{ name: "A", value: "A" }, { name: "B", value: "B" }, { name: "C", value: "C" }];
      $scope.selected = {};
      $scope.options = [{ name: "A" }];

      $scope.makeOptions = function(level1) {
        
        var options = [];
        
        for (var i = 0; i < 5; i++) 
        { 
          options.push({ label: 'Value = ' + i + level1, value: i + level1 }); 
        }
        
        return options;
      };

      $scope.addQuery = function(idx) {
        $scope.options.splice(idx + 1, 0, {});
      };

      $scope.removeQuery = function (idx) {
        $scope.options.splice(idx, 1);
      };


    });
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.js"></script>
<div ng-controller="DemoController" ng-app="demoApp">
  <div>

    <h2>Infinite Digest 2</h2>

    <div ng-repeat="option in options">

      <input type="button" value="+" ng-click="addQuery($index)" />
      <input type="button" value="-" ng-click="removeQuery($index)" />

      <select ng-model="option.level1" ng-options="lvl.name as lvl.name for lvl in level1options" ng-change="optionsLvLs = makeOptions(option.level1)"></select>
      <select ng-model="option.level2" ng-options="opt.value as opt.label for opt in optionsLvLs"></select>

      <br/>
      <br/>

    </div>

  </div>

  {{ options }}

</div>



回答2:

Doing the following changes it won't require to call a function in order to create the data dynamically:

$scope.level1options = [{ name: "A", value: "A" }, { name: "B", value: "B" }, { name: "C", value: "C" }];
$scope.level2options = [0,1,2,3,4];

<select ng-model="selected.level1" ng-options="lvl.name as lvl.name for lvl in level1options"></select>
<select ng-model="selected.level2" ng-options="i+selected.level1 as 'Value = '+i+selected.level1 for i in level2options"></select>

Here's the plunker: http://plnkr.co/edit/GQhnjTpB0sLHoiSnchhn?p=preview