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/
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.
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
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/
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.
});
});
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'}}
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.
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
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
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>
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>
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.
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..
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'}}
Related
I couldnt fint the answer on stack or google...
Why function in ng-bind call many times ?
html:
<li ng-if="byProviders" ng-repeat="(key, value) in byProviderGames | groupBy: 'provider'">
<p ng-bind="providersNames(key)"></p>
</li>
controller:
$scope.providersNames = function providersNames(key) {
// providersObject's length is 8
var index = $scope.providersObject.findIndex(function(x){ return x.name == key });
// Call more then 1000 times
console.log($scope.gamesProviders[index]);
var title = $scope.providersObject[index].title;
return title;
}
From the ngBind source code we can see that this directive registers a ngBindWatchAction callback to change element.textContent whenever the ng-bind attribute changes using scope.$watch:
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
element.textContent = stringify(value);
});
The watchExpression that is used as a first argument of the scope.$watch method is called on every call to $digest() (at least 1 time) and returns the value that will be watched (but it may be executed multiple times to check if the model was not changed by by other watchers). This is mentioned in the docs:
Be prepared for multiple calls to your watchExpression because it will
execute multiple times in a single $digest cycle if a change is
detected.
In this example $scope.getName method will be called 11 times until its returned value become stable:
angular.module('bindExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.name = 'Arik';
$scope.count = 0;
$scope.getName = function() {
if ($scope.count < 10) { $scope.count++; }
console.log($scope.count);
return $scope.name + $scope.count;
};
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="bindExample">
<div ng-controller="ExampleController">
Hello <span ng-bind="getName()"></span>!
</div>
</body>
There are a couple things you can do to help with performance and how it affects the number of times the function is called.
First, you can add a track by $index statement to the end of the ng-repeat. It would end up looking like this.
ng-repeat="(key, value) in byProviderGames | groupBy: 'provider' track by $index"
Second, if just displaying data and no other interaction will take place you can unbind them from the scope. So it'll no longer dirty check for changes. It'll end up looking like this.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider'"
Superpower it with both.
ng-repeat="(key, value) in ::byProviderGames | groupBy: 'provider' track by $index"
You can even go as far as to unbind the actual value.
<p ng-bind="::providersNames(key)"></p>
I'm including a code pen, so you can see how many times the function is called. Nowhere as much as you indicated above. Hope it helps. CodePen
Unbind, properly known by (one-time-binding)[https://docs.angularjs.org/guide/expression]
One-time binding
An expression that starts with :: is considered a one-time expression. One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value (see value stabilization algorithm below).
function exampleController($scope) {
$scope.exampleArray = [1, 2, 3, 4];
var exampleLookupArray = [
{ name: "Schnauzer" },
{ name: "Dachshund" },
{ name: "German Shepard" },
{ name: "Doberman Pinscher" }
];
$scope.breedLookUp = function(key) {
console.count();
return exampleLookupArray[key-1].name;
};
}
angular
.module("example", [])
.controller("exampleController", exampleController);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div class="container-fluid" ng-app="example">
<div class="container" ng-controller="exampleController">
<div ng-repeat="ex in ::exampleArray track by $index">
<span ng-bind="::breedLookUp(ex)"></span>
</div>
</div>
</div>
I have an application that deals with different currencies. I get currency symbol from a web service and store that symbol in the controller's $scope.
$scope.symbol = '€';
When I try to show that in html,
This is working:
{{ 1500 | currency:"€" }}
This is not working
{{ 1500 | currency:symbol }}
here's a plunker. Any ideas?
If you want to bind html or markup you need to use "ng-bind-html" and mark the content as trusted on your controller. I'm unaware of any way of how to do this with the mustache binding mechanism. But this is the approach we've been using when needing to bind markup.
Make the code trusted in the controller
Wrap the filter in a custom filter - Limitation is that you'll still need ng-bind-html
Below are 3 options available to you:
Controller
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$sce) {
$scope.nonTrustedSymbol = '€';
$scope.trustedSymbol = $sce.trustAsHtml('€');
})
.filter('currencyWithNumberFilter', ['$filter','$sce',
function ($filter, $sce) {
return function (input, curr) {
var formattedValue = $filter('number')(input, 2);
return $sce.trustAsHtml(curr + formattedValue);
}
}]
)
.filter('currencyWithCurrencyFilter', ['$filter','$sce',
function ($filter, $sce) {
return function (input, curr) {
var formattedValue = $filter('currency')(input,curr);
return $sce.trustAsHtml(formattedValue);
}
}]
);
Markup
<body ng-controller="MainCtrl">
"Vanilla" controller & number filter:
<span ng-bind-html=trustedSymbol></span>{{ 1500 | number:2 }}
<br/>
Custom filter, internally making use of Number filter for formatting:
<span ng-bind-html="1500 | currencyWithNumberFilter:nonTrustedSymbol"></span>
<br/>
Custom filter, internally making use of Currency filter for formatting:
<span ng-bind-html="1500 | currencyWithCurrencyFilter:nonTrustedSymbol"></span>
</body>
Working sample
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope,$sce) {
$scope.nonTrustedSymbol = '€';
$scope.trustedSymbol = $sce.trustAsHtml('€');
})
.filter('currencyWithNumberFilter', ['$filter','$sce',
function ($filter, $sce) {
return function (input, curr) {
var formattedValue = $filter('number')(input, 2);
return $sce.trustAsHtml(curr + formattedValue);
}
}]
)
.filter('currencyWithCurrencyFilter', ['$filter','$sce',
function ($filter, $sce) {
return function (input, curr) {
var formattedValue = $filter('currency')(input,curr);
return $sce.trustAsHtml(formattedValue);
}
}]
);
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
</head>
<body ng-controller="MainCtrl">
"Vanilla" controller & number filter:
<span ng-bind-html=trustedSymbol></span>{{ 1500 | number:2 }}
<br/>
Custom filter, internally making use of Number filter for formatting:
<span ng-bind-html="1500 | currencyWithNumberFilter:nonTrustedSymbol"></span>
<br/>
Custom filter, internally making use of Currency filter for formatting:
<span ng-bind-html="1500 | currencyWithCurrencyFilter:nonTrustedSymbol"></span>
</body>
</html>
to an addition to Rohan's answer, where ng-bind-html was the solution, you could also use isoCurrency module.
if you have the ISO 4217 currency code (3 chars length e.g. USD, EUR, etc) isoCurrency can output the right format, fraction size and symbol.
// in controller
$scope.amount = 50.50;
$scope.currency = 'USD';
// in template
{{ amount | isoCurrency:currency }} // $50.50
isoCurrency
Demo
Try this in your controller:
$scope.symbol = '€';
Plunker
If using JQuery (Otherwise do the same thing but in pure javascript, it will take much more code but is possible, basically create a html dom element and insert your input as innerHTML to it, then read it's innerText)
function unescapeHTML(html) {
return $("<div />").html(html).text();
}
And then of course
$scope.price=unescapeHTML('€');
To display currency symbol in angular js you need provide HTML entity for currency symbols below are the examples and usage in through code and in template :
Inside your Template example of Euro:
Item Price<span style="font-weight:bold;">{{price | currency:"€"}}</span>
example of Rupee:
Item Price<span style="font-weight:bold;">{{price | currency:"₹"}}</span>
Also check below url
http://www.cs.tut.fi/~jkorpela/html/euro.html
From controller :
Inject $filter in your controller
$scope.price=$filter('currency')($scope.price,'€')
I'm using this directive (wallop-slider-angularjs) and it requires an array of image urls, but my urls are properties of an array of objects. How can I bind the property in such a way that it is acceptable to the directive?
<div ng-repeat="user in users">
<wallop-slider
data-images="??user.media.mediumURL??"
data-animation="rotate"
data-current-item-index="currentSliderIndex"></wallop-slider>
</div>
media = [{'mediumURL':'http://whatever.com/image.jpg'},{'mediumURL':'http://whatever.com/image2.jpg'}]
I solved this with a custom filter:
app.filter('extractProperty', function() {
return function(array, propertyName) {
return array.map(function(item) { return item[propertyName]; });
};
});
To get an array containing the specific property you must use it like that:
<div ng-repeat="user in users">
<wallop-slider data-images="{{ media | extractProperty:'mediumURL' }}"...></wallop-slider>
</div>
Here is a working example:
http://plnkr.co/edit/WpuOCU?p=preview
Just create a function on the scope which will extract the needed properties from the array. Something like:
scope.getMediumUrls = function(arr) {
return $.map(arr, function(item) { return item.mediumURL; });
}
And then use it on the directive:
<div ng-repeat="user in users">
<wallop-slider data-images="getMediumUrls(user.media)"...></wallop-slider>
</div>
You need to loop through the object along with the key and corresponding properties.
Suppose you have JS object as
var media = [
{'mediumURL':'http://whatever.com/image.jpg'},
{'mediumURL':'http://whatever.com/image2.jpg'}
];
Lets apply for in loop for getting values
for(key in media){
alert(media[key].mediumURL);
}
Here "key" refers to index for media[] and "mediumURL" is the individual corresponding property.
In your case,
<div ng-repeat="user in users">
<wallop-slider
data-images="??user.mediumURL??"
data-animation="rotate"
data-current-item-index="currentSliderIndex"></wallop-slider>
</div>
Note: You were using "user.media.mediumURL", hence it wont work because "media" is not property for each object structure under media[].
You can refer to this link for more details on ng-repeat looping examples.
Edit: If you already have a dependency on jQuery, I would pick Shay Friedmans answer.
I think this will do your trick (without the need for any additional libraries).
// Helper function to pluck the url property from the media items.
function pluckUrls() {
var ret = [], c = $scope.mediaItems.length;
while(c--) {
ret.push($scope.mediaItems[c].url);
}
return ret;
}
// Function that is called each watch cycle. The return value will differ
// if one of the urls has been modified.
function getUniqueWatchValue() {
return pluckUrls().join();
}
// Function that is called whenever one of the urls has been modified.
function watchValueChanged() {
console.log('One of the urls has been modified');
$scope.mediaUrls = pluckUrls();
}
// Hook up the watch.
$scope.$watch(getUniqueWatchValue, watchValueChanged);
Plunker in action can be found here.
Note: I noticed a piece of code in that wallop-slider thingy that is watching a reference to the array, not it contents. I haven't tested it but it probably requires you to recreate the array completely instead of simply adding or removing element from it.
$scope.$watch('images', function(images) {
if (images.length) {
_goTo(0);
}
});
The easiest way to do is using underscore like the following:
<wallop-slider
data-images="_.pluck(user.media.mediumURL, 'mediumURL')"
data-animation="rotate"
data-current-item-index="currentSliderIndex"></wallop-slider>
</div>
But before that you need do 2 things:
add underscore
<script src="/whatever/underscore.js"></script>
inject underscorejs into controller like the folloiwng
angular.module('app', [])
.controller('Ctrl', function($scope, $window) {
$scope._ = $window._
});
Hope that help,
Ron
How can I check if an angular model is an array or an object? Is there a built in or do I have to write a filter isArray with Array.isArray()
{{[] | isArray}} doesn't work
You can use the angular.isArray function. It's built-in inside Angularjs.
If you want to use this function inside your template, you have to create a custom filter: http://docs.angularjs.org/tutorial/step_09
Example of what you want:
angular.module('...', []).filter('isArray', function() {
return function (input) {
return angular.isArray(input);
};
});
Then you can use the filter inside your template:
{{ myVar | isArray }}
I guess you can also add underscore/lodash to the rootScope and use it:
_ = require('lodash')
angular.module('app',[])
.run(function($rootScope){
$rootScope._ = _
})
And in the template:
<div ng-show="$root._.isArray(foo)">
<label> First element of Foo </label>
<span> {{ $root._.first(foo) }} </span>
</div>
The benefits - you have to add lodash only in one place, and it will be available everywhere. You can use many other things the same way, like Math functions for example. Just be reasonable and don't put too much javascript into expressions.
I'm trying to do something in Angular like this:
Your search found {{ searchResults.iTotalRecords > 0 , 0 }} results.
Certainly this doesn't work but you get the idea. If my object is an integer, how can I check if the value is greater than zero or else set it to 0?
Thanks.
Custom Angular filters are well-suited for this purpose if you only want to change the presentation of the data, without changing the underlying (real) data in your application.
Below is an example filter for your use-case. You could put any other logic in your filter.
HTML:
<p>Your search found {{searchResults.iTotalRecords | resultCountFilter}} results.</p>
Javascript:
angular.module('app', []).filter('resultCountFilter', function () {
return function (resultCount) {
return resultCount > 0 ? resultCount : 0;
};
});
Here's a JSFiddle: http://jsfiddle.net/alexross/ZN87E/
AngularJS Docs on Filters: Understanding Angular Filters
To do that you can just use a function on your $scope:
app.controller('MainCtrl', function($scope) {
$scope.foo = function() {
return $scope.searchResults.iTotalRecords > 0 ? $scope.searchResults.iTotalRecords : 0;
};
});
And in your markup:
<span>{{foo()}}</span>
EDIT: The 100% markup way...
<span ng-show="searchResults.iTotalRecords > 0">{{searchResults.iTotalRecords}}</span>
<span ng-show="searchResults.iTotalRecords <= 0">0</span>
You might also want to check on ngSwitch and ngHide for more advanced cases like this.