236

Is there a way to use math functions in AngularJS bindings?

e.g.

<p>The percentage is {{Math.round(100*count/total)}}%</p>

This fiddle shows the problem

http://jsfiddle.net/ricick/jtA99/1/

Graham
  • 7,431
  • 18
  • 59
  • 84
ricick
  • 5,694
  • 3
  • 25
  • 37

13 Answers13

335

You have to inject Math into your scope, if you need to use it as $scope know nothing about Math.

Simplest way, you can do

$scope.Math = window.Math;

in your controller. Angular way to do this correctly would be create a Math service, I guess.

Tosh
  • 35,955
  • 11
  • 65
  • 55
  • 1
    Thanks. I have a use case where I have multiple templates with completely different logic and want to isolate them as much as possible. Perfect – Yablargo Nov 06 '13 at 02:16
  • 49
    You should use a filter, not put the Math object on the scope. – Soviut Dec 01 '14 at 19:08
  • 4
    This is good for quick and dirty things; quickly mocking out something or wanting to see how something works. Which is probably what someone who wants to do math in their html template files wants. – Lan Jul 02 '15 at 14:04
  • 1
    Using functions in bindings will make the function to call in each scope's update and will use too much resources. – Hossein Sep 20 '16 at 22:09
  • This solution is *wrong*. why? 1- Polluting the global scope is the worst idea ever. 2- views are responsible for `formatting`, so use filter instead. – Afshin Mehrabani Oct 03 '16 at 16:20
  • If you're going to do that, then might as well use the $window service so it can be mocked in unit tests. – Van Nguyen Nov 17 '16 at 00:43
308

While the accepted answer is right that you can inject Math to use it in angular, for this particular problem, the more conventional/angular way is the number filter:

<p>The percentage is {{(100*count/total)| number:0}}%</p>

You can read more about the number filter here: http://docs.angularjs.org/api/ng/filter/number

Andrew Kuklewicz
  • 10,621
  • 1
  • 34
  • 42
  • 8
    Not only is this the Angular way, it's the correct way. It's the view's responsibility to 'format' the value. – Luke Dec 20 '13 at 00:45
  • 40
    Andrew, sorry to clutter your post with this comment. Your answer is good and helpful; however, I wish to disagree with the other comments. I often cringe when people tout the "angular way" as though it is some paragon of perfect development. It isn't. The OP asked in general how to include math functions in a binding, with rounding presented only as an example. While this answer may be "the angular way" by purists to solve exactly one math problem, it does not fully answer the question. – kbrimington Sep 25 '14 at 22:47
  • 1
    Sorry for archeology, but this is incorrect when you need a Number as output. `{{1234.567|number:2}}` renders as `1,234.57` or `1 234,57`. If you try to pass it to `` you will empty input, as the value IS NOT A CORRECT NUMBER, but locale dependent string representation. – SWilk Mar 30 '17 at 14:16
62

I think the best way to do it is by creating a filter, like this:

myModule.filter('ceil', function() {
    return function(input) {
        return Math.ceil(input);
    };
});

then the markup looks like this:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>

Updated fiddle: http://jsfiddle.net/BB4T4/

Shadow The GPT Wizard
  • 66,030
  • 26
  • 140
  • 208
sabinstefanescu
  • 636
  • 5
  • 4
  • The number filter as given in klaxon's answer will do the ceil already, so no need for that. – Kim Dec 09 '16 at 15:13
  • I did something similar, only using the filter to create the timer itself as I wanted it. In other words, I'd take the current time and format it into a string as I saw fit using a custom filter. Also the number filter is good when you want to round your numbers, but for floor and ceiling functions it's useless. – Gabriel Lovetro Jan 30 '18 at 15:55
37

This is a hairy one to answer, because you didn't give the full context of what you're doing. The accepted answer will work, but in some cases will cause poor performance. That, and it's going to be harder to test.

If you're doing this as part of a static form, fine. The accepted answer will work, even if it isn't easy to test, and it's hinky.

If you want to be "Angular" about this:

You'll want to keep any "business logic" (i.e. logic that alters data to be displayed) out of your views. This is so you can unit test your logic, and so you don't end up tightly coupling your controller and your view. Theoretically, you should be able to point your controller at another view and use the same values from the scopes. (if that makes sense).

You'll also want to consider that any function calls inside of a binding (such as {{}} or ng-bind or ng-bind-html) will have to be evaluated on every digest, because angular has no way of knowing if the value has changed or not like it would with a property on the scope.

The "angular" way to do this would be to cache the value in a property on the scope on change using an ng-change event or even a $watch.

For example with a static form:

angular.controller('MainCtrl', function($scope, $window) {
   $scope.count = 0;
   $scope.total = 1;

   $scope.updatePercentage = function () {
      $scope.percentage = $window.Math.round((100 * $scope.count) / $scope.total);
   };
});
<form name="calcForm">
   <label>Count <input name="count" ng-model="count" 
                  ng-change="updatePercentage()"
                  type="number" min="0" required/></label><br/>
   <label>Total <input name="total" ng-model="total"
                  ng-change="updatePercentage()"
                  type="number" min="1" required/></label><br/>
   <hr/>
   Percentage: {{percentage}}
</form>

And now you can test it!

describe('Testing percentage controller', function() {
  var $scope = null;
  var ctrl = null;

  //you need to indicate your module in a test
  beforeEach(module('plunker'));

  beforeEach(inject(function($rootScope, $controller) {
    $scope = $rootScope.$new();

    ctrl = $controller('MainCtrl', {
      $scope: $scope
    });
  }));

  it('should calculate percentages properly', function() {
    $scope.count = 1;
    $scope.total = 1;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(100);

    $scope.count = 1;
    $scope.total = 2;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(50);

    $scope.count = 497;
    $scope.total = 10000;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(5); //4.97% rounded up.

    $scope.count = 231;
    $scope.total = 10000;
    $scope.updatePercentage();
    expect($scope.percentage).toEqual(2); //2.31% rounded down.
  });
});
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • you could compile the element and then test the value found in the dom element. that's actually preferred since you might be using localization filters, too. – FlavorScape Aug 07 '14 at 04:02
  • Good answer. But wondering why prepend with `$window` since it seems to work with just plan `Math.round()`? – Neel Jul 19 '15 at 13:51
  • 3
    @Neel - $window allows you to more easily mock the `Math` for tests. Alterinatively, you could create a Math service and inject it. – Ben Lesh Jul 27 '15 at 20:32
8

Better option is to use :

{{(100*score/questionCounter) || 0 | number:0}}

It sets default value of equation to 0 in the case when values are not initialized.

klaxon
  • 415
  • 6
  • 10
8

Why not wrap the whole math obj in a filter?

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


function math() {
    return function(input,arg) {
        if(input) {
            return Math[arg](input);
        }

        return 0;
    }
}

return app.filter('math',[math]);

and to use:

{{ number_var | math:'ceil'}}

Marcos Ammon
  • 81
  • 1
  • 2
6

If you're looking to do a simple round in Angular you can easily set the filter inside your expression. For example:

{{ val | number:0 }}

See this CodePen example & for other number filter options.

Angular Docs on using Number Filters

Peter Girnus
  • 2,673
  • 1
  • 19
  • 24
6

The easiest way to do simple math with Angular is directly in the HTML markup for individual bindings as needed, assuming you don't need to do mass calculations on your page. Here's an example:

{{(data.input/data.input2)| number}} 

In this case you just do the math in the () and then use a filter | to get a number answer. Here's more info on formatting Angular numbers as text:

https://docs.angularjs.org/api/ng/filter

Sara Inés Calderón
  • 1,070
  • 12
  • 22
5

Either bind the global Math object onto the scope (remember to use $window not window)

$scope.abs = $window.Math.abs;

Use the binding in your HTML:

<p>Distance from zero: {{abs(distance)}}</p>

Or create a filter for the specific Math function you're after:

module.filter('abs', ['$window', function($window) {
  return function(n) {
    return $window.Math.abs($window.parseInt(n));
  };
});

Use the filter in your HTML:

<p>Distance from zero: {{distance | abs}}</p>
craig
  • 285
  • 2
  • 10
3

That doesn't look like a very Angular way of doing it. I'm not entirely sure why it doesn't work, but you'd probably need to access the scope in order to use a function like that.

My suggestion would be to create a filter. That's the Angular way.

myModule.filter('ceil', function() {
    return function(input) {
        return Math.ceil(input);
    };
});

Then in your HTML do this:

<p>The percentage is {{ (100*count/total) | ceil }}%</p>
7ahid
  • 420
  • 1
  • 5
  • 12
2

The number filter formats the number with thousands separators, so it's not strictly a math function.

Moreover, its decimal 'limiter' doesn't ceil any chopped decimals (as some other answers would lead you to believe), but rather rounding them.

So for any math function you want, you can inject it (easier to mock than injecting the whole Math object) like so:

myModule.filter('ceil', function () {
  return Math.ceil;
});

No need to wrap it in another function either.

0

This is more or less a summary of three answers (by Sara Inés Calderón, klaxon and Gothburz), but as they all added something important, I consider it worth joining the solutions and adding some more explanation.

Considering your example, you can do calculations in your template using:

{{ 100 * (count/total) }}

However, this may result in a whole lot of decimal places, so using filters is a good way to go:

{{ 100 * (count/total) | number }}

By default, the number filter will leave up to three fractional digits, this is where the fractionSize argument comes in quite handy ({{ 100 * (count/total) | number:fractionSize }}), which in your case would be:

{{ 100 * (count/total) | number:0 }}

This will also round the result already:

angular.module('numberFilterExample', [])
  .controller('ExampleController', ['$scope',
    function($scope) {
      $scope.val = 1234.56789;
    }
  ]);
<!doctype html>
<html lang="en">
  <head>  
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
  </head>
  <body ng-app="numberFilterExample">
    <table ng-controller="ExampleController">
      <tr>
        <td>No formatting:</td>
        <td>
          <span>{{ val }}</span>
        </td>
      </tr>
      <tr>
        <td>3 Decimal places:</td>
        <td>
          <span>{{ val | number }}</span> (default)
        </td>
      </tr>
      <tr>
        <td>2 Decimal places:</td>
        <td><span>{{ val | number:2 }}</span></td>
      </tr>
      <tr>
        <td>No fractions: </td>
        <td><span>{{ val | number:0 }}</span> (rounded)</td>
      </tr>
    </table>
  </body>
</html>

Last thing to mention, if you rely on an external data source, it probably is good practise to provide a proper fallback value (otherwise you may see NaN or nothing on your site):

{{ (100 * (count/total) | number:0) || 0 }}

Sidenote: Depending on your specifications, you may even be able to be more precise with your fallbacks/define fallbacks on lower levels already (e.g. {{(100 * (count || 10)/ (total || 100) | number:2)}}). Though, this may not not always make sense..

Kim
  • 1,361
  • 3
  • 18
  • 24
0

Angular Typescript example using a pipe.

math.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'math',
})

export class MathPipe implements PipeTransform {

  transform(value: number, args: any = null):any {
      if(value) {
        return Math[args](value);
      }
      return 0;
  }
}

Add to @NgModule declarations

@NgModule({
  declarations: [
    MathPipe,

then use in your template like so:

{{(100*count/total) | math:'round'}}
Joel Davey
  • 2,363
  • 1
  • 21
  • 20