Dynamically add $watch

梦想与她 提交于 2020-01-06 03:30:28

问题


I have a JSON dataset that each row includes an input value, a small block of JavaScript code used to calculate and a subtotal value that is the result of the input value after an eval() preforms the calculation. The dataset will contain one or more rows. Each input will be repeated onto the page in HTML and a sum of the subtotals will be shown to the user as a total value along with each separate subtotal value.

I tried to use $watch and add one for each row in the repeater but I can't seem to get them to fire as the user changes the input value.

I have created my first Sample_Plunker to demonstrate what I'm trying to achieve with no success.

Not sure if I should also be posting the code here or not but any help would be greatly appreciated.

Basically my HTML:

<div ng-controller="MainCtrl as MainCtrl" ng-init="MainCtrl.init()">
    <div ng-repeat="rule in MainCtrl.myCode">

        Input_{{$index}}: <input ng-model="rule.inpValue" type="number" />
        <!-- the following needs to reflect the result after eval() of myCode[?].code from JSON below -->
        Subtotal: {{rule.nSubTotal}}
        <br />
    </div>
    <br />

    <!-- the following should be a sum of all values above -->
    Total: {{MainCtrl.nTotal}}

</div>

This is my sample data and $watch that is not responding:

app.controller('MainCtrl', function($scope) {
    var _this = this;
    _this.nTotal = 0;
    // sample js code tht will be execuited later
    _this.myCode = [{
        "code": "_this.myCode.inpValue +2",
        "inpValue": 0,
        "nSubTotal": 0
    }, {
        "code": "_this.myCode.inpValue*3",
        "inpValue": 0,
        "nSubTotal": 0
    }, {
        "code": "_this.myCode.inpValue/5",
        "inpValue": 0,
        "nSubTotal": 0
    }];

    this.init = function() {
        $scope.$watch('MainCtrl.myCode[i].inpValue', function() {
            // debugger;

            // assuming if watch would fire, subtotal = eval( input ) 
            _this.nSubTotal = eval(_this.myCode[i].code);

            // I would also keep a running total at this point
            _this.nTotal = _this.nTotal + _this.myCode[i].nSubTotal;
        });
    }; //end init()
});

回答1:


There's a lot wrong with your code, but basically to answer your question, you can apply a watch to each of the items in an array by looping through the array and calling $scope.$watch once for each element.

I also strongly advise you to use actual functions and not eval() in order to evaluate expressions.

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

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

  var _this = this;
  _this.nTotal = 0;

  // sample js code tht will be execuited later
  _this.myCode = [{
    "code": function() { return this.inpValue + 2; },
    "inpValue": 0,
    "nSubTotal": 0
  }, {
    "code": function() { return this.inpValue * 3; },
    "inpValue": 0,
    "nSubTotal": 0
  }, {
    "code": function() { return this.inpValue / 5; },
    "inpValue": 0,
    "nSubTotal": 0
  }];

  function sum (values) {
      return values.reduce(function (a, b) { return a + b; }, 0);
  }

  this.init = function() {

    _this.myCode.forEach(function(code) {
      $scope.$watch(function() {
        return code.inpValue;
      }, function() {
        code.nSubTotal = code.code();
        _this.nTotal = sum(_this.myCode.map(function(c) { return c.nSubTotal; }));
      });
    });

  };

});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js" data-semver="1.4.7" data-require="angular.js@1.4.x"></script>
<div ng-app="plunker" ng-controller="MainCtrl as MainCtrl" ng-init="MainCtrl.init()">
  <div ng-repeat="rule in MainCtrl.myCode">

    Input_{{$index}}:
    <input ng-model="rule.inpValue" type="number" />

    Subtotal: {{rule.nSubTotal}}
    <br />

  </div>
  <br />

  <!-- the following should be a sum of all values above -->
  Total: {{MainCtrl.nTotal}}

  <br />
  <br />

</div>



回答2:


Your $watch is not firing because you are watching an expression which will always evaluated to undefined against the $scope. You have a few options when it comes to watching a set of items for changes:

  1. A separate $watch for each item. Not very efficient, especially if the array is large.
  2. A $watchGroup, which accepts an array of watch expressions. Useful when the expressions are non-uniform, for example different properties of different objects.
  3. A $watchCollection, which shallow-watches a single object. I think this works best for your situation.

Also, note that the first argument to $watch* can be a function which returns a value you would like to watch. Putting this together, your watcher can look like

$scope.$watchCollection(function() {
  var aInputs = [];
  for (var i = 0, len = myCode.length; i < len; ++i) {
    aInputs.push(myCode[i].inpValue);
  }
  return aInputs;
}, function() {
  // one of the inpValues in myCode has changed
  // need to re-compute nTotal
});

Also, I strongly recommend you do not use eval() and follow JLRishe's suggestion of using functions. If you absolutely need to evaluate string expressions, you can do this safely with Angular's $scope.$eval. This evaluates expressions against a scope and a set of local variables. For example,

$scope.$eval("inpValue + 2", { inpValue: 3 }) === 5;

Here is a working Plunker. It uses $eval as a proof of concept, but again, if you can, use plain functions.



来源:https://stackoverflow.com/questions/33311105/dynamically-add-watch

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!