Chen
Chen

Reputation: 23

angular computed variable from an expression

I have a requirement to dynamic show the sub total based on a expression returned from the service. See the sample item structure below. property value is binding to a text input. If any of the input value changes, sub total is updated according to the expression in a label. What's the best way to do this?

Note: number of fieldname/Value pair can vary.

$scope.item = [
  {fieldName: "fname1", value: 2},
  {fieldName: "fname2", value: 5},
  {fieldName: "fname3", value: 4},
  {fieldName: "fname4", value: 6},
  {fieldName: "fname5", value: 3},
  {fieldName: "subTotal1", expression: "['fname1'] + ['fname2'] +['fname3'] +['fname4'] +['fname5'] +"}
]

Upvotes: 1

Views: 1140

Answers (3)

Stepan Kasyanenko
Stepan Kasyanenko

Reputation: 3186

Oh my God! I did it! Look :)

Of course, this solution is not perfect. And it depends on the variable name used in the controller. But it works!

Live example on jsfiddle.

<form name="ExampleForm" id="ExampleForm">
  <div ng-repeat="item in items">
    <div ng-if="item.fieldName!='subTotal1'">
      <input ng-model="item.value">
      <my-ng-model n-name="{{item.fieldName}}" n-value="item.value" obj="obj"></my-ng-model>
    </div>
    <div ng-if="item.fieldName=='subTotal1'">
      {{item.expression }}={{$eval(item.expression)}}
    </div>
  </div>
  {{obj|json}}
</form>

And js controller:

.controller('ExampleController', function($scope, $parse) {
$scope.obj = {};
$scope.items = [{
  fieldName: "fname1",
  value: 2
}, {
  fieldName: "fname2",
  value: 5
}, {
  fieldName: "fname3",
  value: 4
}, {
  fieldName: "fname4",
  value: 6
}, {
  fieldName: "fname5",
  value: 3
}, {
  fieldName: "subTotal1",
  expression: "obj.fname1 + obj.fname2 +obj.fname3 +obj.fname4 +obj.fname5"
}];})

And js directive:

.directive('myNgModel', function() {
var root = {
  restrict: "E",
  replace: true,
  scope: {
    nName: "@",
    nValue: "=",
    obj: "="
  },
  template: '<div></div>',
  link: function(scope) {
    scope.obj[scope.nName] = scope.nValue*1;
    scope.$watch('nValue', function(value) {
      scope.obj[scope.nName] = value*1;
    });
  }
}
return root; })

UPDATED

Now it works without reference to a local variable!

Live example on jsfiddle.

<form name="ExampleForm" id="ExampleForm">
  <div ng-repeat="item in items">
    <div ng-if="item.fieldName!='subTotal1'">
      <input ng-model="item.value">
      <my-ng-model n-name="{{item.fieldName}}" n-value="item.value" obj="obj"></my-ng-model>
    </div>
  </div>
  <div ng-repeat="eval in evals">
    {{eval.expression }}={{$eval(eval.expression,obj)}}
  </div>
</form>

Controller

.controller('ExampleController', function($scope, $parse) {
$scope.obj = {};
$scope.items = [{
  fieldName: "fname1",
  value: 2
}, {
  fieldName: "fname2",
  value: 5
}, {
  fieldName: "fname3",
  value: 4
}, {
  fieldName: "fname4",
  value: 6
}, {
  fieldName: "fname5",
  value: 3
}, {
  fieldName: "subTotal1",
  expression: "fname1 + fname2 +fname3 +fname4 +fname5"
}];
$scope.evals = [];
angular.forEach($scope.items, function(item) {
  if (item.expression) {
    $scope.evals.push({
      expression: item.expression
    });
  }
});})

And directive

.directive('myNgModel', function() {
var root = {
  restrict: "E",
  replace: true,
  scope: {
    nName: "@",
    nValue: "=",
    obj: "="
  },
  link: function(scope) {
    scope.obj[scope.nName] = scope.nValue * 1;
    scope.$watch('nValue', function(value) {
      scope.obj[scope.nName] = value * 1;
    });
  }
}
return root;})

Upvotes: 1

Cosmin
Cosmin

Reputation: 2214

I wrote a fiddle to solve your problem. It can pe improved, but I don't have time at the moment. You can do that yourself.

It is based on the model you posted :

  $scope.item = [
    {fieldName: "fname1", value: 2},
    {fieldName: "fname2", value: 5},
    {fieldName: "fname3", value: 4},
    {fieldName: "fname4", value: 6},
    {fieldName: "fname5", value: 3},
    {fieldName: "subTotal1", expression: "['fname1'] + ['fname2'] +['fname3'] +['fname4'] +['fname5'] +"}
  ]

For binding to work you can put everything in a function that is re-evaluated when a property changes.

Check out it HERE

Upvotes: 0

Charlie
Charlie

Reputation: 23798

If you need to compute the value based on the expresion property, you should first tokanize the field indicators together with their mathematical operators in the expression. Complexity of this process is varied by the types of mathematical impressions you allow in the expression.

Eg:

[fName1] + [fName2] - [fName3]    // is a simple expression
[fName1] * ([fName2] + [fName3])  // is complex than that

Then you should compute the aggregated value out of these tokens by comparing them against the array.

--

One alternative way, which I don't recommend if the expression is coming from the user, is to create a private scope object which has all these fName variables inside it and use eval function against that context via abstraction.

You can get an idea about this method in this answer.

Upvotes: 0

Related Questions