Nick
Nick

Reputation: 382

AngularJS - how to sync result of calculated input field to a scope variable

I'm trying to sync the result of a calculated form field into a scope variable. For example:

<div class="form-group">
  <label for="val1" class="control-label">Val 1</label>
  <input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
  <label for="val2" class="control-label">Val 2</label>
  <input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
  <label for="score" class="control-label">Score</label>
  <input class="form-control" name="score" value="{{data.val1+data.val2}}" type="text" id="score">
</div>
<br/>data: {{data}}

How can I sync the result (i.e. the score field) into the scope variable $scope.data.score? I have tried ng-model="data.score" but that breaks the calculation.

You can see the example in action here: http://plnkr.co/edit/fc9XcyyYGtAk0aGVV35t?p=preview

How do I get the last line to read data: {"val1":1,"val2":2,"score":3}?

Note that I'm looking for a solution that involves minimal to no code support at the controller level. For example, I know you can set up a watch in the controller for both val1 and val2 and then update the score in the watcher. This is what I wanted to avoid, if it's possible in angular at all. If it's not (theoretically) possible, I'd really appreciate an explanation of why it's not.

A quick background might help. Basically we have a simple form builder app that defines a form and all its fields in an xml file. Here's a sample of what the xml would look like:

<form name="test">
    <field name="val1" control="textbox" datatype="number">
        <label>Val 1</label>
    </field>
    <field name="val2" control="textbox" datatype="number">
        <label>Val 2</label>
    </field>
    <field name="score" control="textbox" datatype="number">
        <label>Score</label>
        <calculate>[val1]+[val2]</calculate>
    </field>    
</form>

When a form is requested, the system will need to pick up the xml, loop through all the fields and generate an angular style html to be served to the browser and processed by angular. Ideally, I want to keep all the form specific logic (validation, show/hide, calculation etc) confined to the html, and keep the controller (js) logic generic for all forms.

The only solution I can come up with is to dynamically load watcher functions, through something like this: eval("$scope.$watch('[data.val1,data.val2]')..."), but as I said, I really want to avoid this, because it's just tedious, and feels extremely dodgy :)

Upvotes: 0

Views: 3273

Answers (3)

Ilya Dmitriev
Ilya Dmitriev

Reputation: 1720

The first dirty way.

In your case you can move all logic from controller into html with using combination of ng-init and ng-change directives.

<div class="form-group">
  <label for="val1" class="control-label">Val 1</label>
  <input class="form-control" name="val1" type="number" ng-model="data.val1" ng-change="data.score = data.val1 + data.val2" id="val1">
</div>
<div class="form-group">
  <label for="val2" class="control-label">Val 2</label>
  <input class="form-control" name="val2" type="number" ng-model="data.val2" ng-change="data.score = data.val1 + data.val2" id="val2">
</div>
<div class="form-group" ng-init="data.score = data.val1 + data.val2">
  <label for="score" class="control-label">Score</label>
  <input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}

I don't think that it's the clearest solution, but you can leave your controller without any changes with it.

Demo on plunker.

The second dirty way.

There is one more way, but now you don't need ng-init and ng-change directives. You can add just one dirty expression in html:

<div id="main-container" class="container" style="width:100%" ng-controller="MainController">
{{data.score = data.val1 + data.val2;""}} <!-- key point -->
<div class="form-group">
  <label for="val1" class="control-label">Val 1</label>
  <input class="form-control" name="val1" type="number" ng-model="data.val1" id="val1">
</div>
<div class="form-group">
  <label for="val2" class="control-label">Val 2</label>
  <input class="form-control" name="val2" type="number" ng-model="data.val2" id="val2">
</div>
<div class="form-group">
  <label for="score" class="control-label">Score</label>
  <input class="form-control" name="score" ng-model="data.score" type="text" id="score">
</div>
<br/>data: {{data}}

;"" in expression stops evaluating of angular expression to text in html.

Demo on plunker.

Upvotes: 2

nitin
nitin

Reputation: 156

If you want to know how you can do it, then i have one solution for you make a ng-change event for both of your text box and sum both the number there and use ng-model in the third text box then, you can see it will work as per your need.

For the first time load you need to calculate it out side only.

Upvotes: 0

Debasish Mohapatra
Debasish Mohapatra

Reputation: 856

See if this works, in your HTML change,

 <input class="form-control" name="score"  ng-model = "data.score" type="text" id="score">

and, in your controller do,

var myApp = angular.module('myapp', [])

    .controller('MainController', function($scope) {
        $scope.data = { val1: 1, val2: 2, score: 3};

        $scope.$watch('[data.val1,data.val2]', function (newValue, oldValue) {
              $scope.data.score = newValue[0] + newValue[1];
        }, true);
})

Demo plunk, http://plnkr.co/edit/gS0UenjydgId4H5HwSjL?p=preview

Upvotes: 0

Related Questions