Create array of objects with function actions - javascript-objects

Is it possible to create an array of objects that perform a function?
For example, like this:
<script>
console.clear();
const acts = [
{ 'inc' : (x) => x+1 },
{ 'dec' : (x) => x-1 }
];
console.log(acts.inc(10));
console.log(acts.dec(15));
</script>
The above gives an error -- TypeError: acts.inc is not a function.

You are almost there! Create an object with "function properties" instead of an array of objects:
acts = {
'inc' : (x) => x+1,
'dec' : (x) => x-1
};
Then,
acts.inc(3)
will return:
4
In your original code, you'd have to use:
acts[0].inc(3)
because it's an array...

You can add a function to the array prototype.
Array.prototype.inc = <function>

For anyone interested, here is how I used the solution chosen along with an example output for each function created (with some options).
<!DOCTYPE html><html lang="en"><head>
<title> Array of Object Functions </title>
<meta charset="UTF-8">
<meta name="viewport" content="width-device-width,initial-scale=1.0, user-scalable=yes"/>
<!-- From: https://stackoverflow.com/questions/62824415/create-array-of-objects-with-function-actions -->
<style>
body { font-size: 1.2em; line-height: 1.6; }
</style>
</head><body>
<pre id='demo'></pre>
<script>
console.clear();
// following used only for screen display testing purposes
var former = console.log; // retain former actions of console.log
var demo=document.getElementById('demo');
console.log = function(...msg){
former(...msg); // maintains existing logging via the console. (optional)
demo.append(msg.join(' ')+'\n'); // output needs to be specified
}
// end of display testing (also shows on console.log display)
const acts = {
'inc' : (x) => x+1,
'dec' : (x) => x-1,
'mul' : (x,y) => x*y,
'div' : (x,y) => x/y, // careful with y=0 (no checks here)
'pwr' : (x,y) => x**y, // x to power of y
'deg' : (rad) => (rad * Math.PI / 360), // degrees from radians
'rad' : (deg) => (deg / Math.PI * 360), // radians from degrees
'pct' : (amt) => amt * 100, // form percentage
'bit' : (siz) => (acts.pwr(2,siz)-1).toString(2), // form bit-mask
'oct' : (dec) => dec.toString(8), // form octal
'hex' : (dec) => dec.toString(16), // form hexidecimal
'fmt' : (amt,size) => amt.toFixed(size), // number .toFixed(#) string
'cdt' : (dateInfo,locn='en-US') => dateInfo.toLocaleDateString(locn), // format US Date object
};
console.log(acts.inc(10)); // 11
console.log(acts.dec(15)); // 14
console.log(acts.mul(4,5)); // 20
console.log(acts.div(5,4)); // 1.25
console.log(acts.pwr(2,16)); // 65536
console.log(acts.deg(360)); // 3.14159...
console.log(acts.rad(Math.PI/2)); // 1.57079...
console.log(`${acts.pct(0.15)}%`); // 15%
console.log(`${acts.pct(acts.div(7,8))}%`); // 87.5%
console.log('0b'+acts.bit(8)); // 0b11111111
console.log('10.0 = 0o'+acts.oct(10),'octal'); // 0o12
console.log('0x'+acts.hex(65535)); // 0xffff
console.log(acts.fmt(Math.PI,2)); // 3.14
console.log(acts.fmt(Math.PI,4)); // 3.1416
console.log(Math.PI); // full precision PI
console.log('m/d/yyyy : ',acts.cdt(new Date())); // current date (m/d/year format) US
console.log('dd/mm/yyyy : ',acts.cdt(new Date(),'en-GB')); // date (dd/mm/year format) GB
// tests of binary range displays
console.log('\n ','----+'.repeat(6)+'--');
console.log('0b'+acts.bit(0)); // 0b0
console.log('0b'+acts.bit(0).padStart(8,'0')); // 0b00000000
console.log('0b'+acts.bit(16)); // 0b1111...1111 (length = 16 bits)
console.log('0b'+acts.bit(16).padStart(32,'0')); // 0b0111...1111
console.log('0b0'+acts.bit(31)); // 0b11111...11111 (length = 31 bits)
console.log('0b'+acts.bit(32).padStart(32,'0')); // 0b1111...1111 ( = 32 bits)
console.log('\n ','----+'.repeat(6)+'--');
console.log(`Dad's birthday: ${acts.cdt(new Date(1925,1,1))}`); // default US format
</script>
</body>
</html>
Additional comments are welcome.

Related

Conditionally adding additional elements in D3

I've built out a heatmap with D3 in React and the basic result works fine: the rects are rendering and so are the conditional color values, based on my data.
Here is roughly what it looks like:
const dayRects = bounds.selectAll("rect").data(processed)
dayRects
.join("rect")
.attr("x", d => xScale(d.exampleData))
.attr("width", ordinalScale.bandwidth)
.attr("y", d => yScale(d.exampleData))
.attr("height", ordinalScale.bandwidth)
.attr("rx", 3)
.style("fill", d => colorScale(d.moreData))
What I'm struggling to do and cannot find guidance for online, is to conditionally add elements based on data. For example, I'm trying to add an additional rect for items in my array of data that meet certain conditions. This additional rect would be slightly larger than the above rects, have a transparent fill, and a different colored stroke - resulting in, for example, something like this:
When I attempt to do this with something like below the above code
const dayStarted = bounds
.select("rect")
.data(processed.filter(i => i.value === desiredValue))
.join("rect")
.attr("x", d => xScale(d.exampleData))
.attr("width", ordinalScale.bandwidth)
.attr("y", d => yScale(d.exampleData))
.attr("rx", 20)
... it just manipulates the first rect element in the first set of rects (dayRects).
You should bind your data to a g element, position that and then add the rects as sibling in the g.
Here's a quick example:
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v6.min.js"></script>
</head>
<body>
<svg width="125" height="125"></svg>
<script>
var processed = [
{ x: Math.random() * 100, y: Math.random() * 100, value: 1 },
{ x: Math.random() * 100, y: Math.random() * 100, value: 0 },
];
var bounds = d3.select('svg');
const g = bounds
.selectAll('g')
.data(processed)
.join('g')
.attr('transform', (d) => {
// use your scales to position g
return "translate(" + d.x + "," + d.y + ")";
});
g.append('rect') // first rect
.attr('width', 20)
.attr('height', 20)
.attr('rx', 3)
.style('fill', 'orange');
g.filter( d => d.value == 1) //apply filter on g for ones that get 2nd rect
.append('rect')
.attr('width', 20)
.attr('height', 20)
.attr('rx', 3)
.style('fill', 'none')
.style('stroke', 'steelblue')
.style('stroke-width', '3px');
</script>
</body>
</html>

React isNaN with input type number

I need to check for NaN before I store a value with useState in my React Hooks application, but it doesn't seem to be checking?
const handleStartValueChange = (value) => {
if (isNaN(parseInt(value)) ) {
setStartValue(0);
}
setStartValue(parseInt(value));
};
This is called from onChange on an input box with type number
<div className="startValueHeader">Start value</div>
<input
value={startValue}
type="number"
className="startValueValue"
onChange={(event) => {
handleStartValueChange(event.target.value);
}}
/>
What am I missing?
The reason I need to check for NaN is I need to store a value of 0 instead of '' when I clear the input field
Number - convert numeric strings and null to numbers
Number('123') // 123
Number('12.3') // 12.3
Number('12.00') // 12
Number('123e-1') // 12.3
Number('') // 0 <--- you need this
Number(null) // 0
Number('0x11') // 17
Number('0b11') // 3
Number('0o11') // 9
Number('foo') // NaN
Number('100a') // NaN
Number('-Infinity') //-Infinity
const handleStartValueChange = value => {
const number = Number(value); // converts '' from empty input -> 0
setStartValue(number)
};
NOTE: If you instead specify defaultValue the input won't display the "0" when empty.

How to create and set a setMapTypeId using react-google-maps

I was looking for a way to create my own mars map in a website, using google maps.
I found this example in google map api
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 0, lng: 0},
zoom: 1,
streetViewControl: false,
mapTypeControlOptions: {
mapTypeIds: ['moon']
}
});
var moonMapType = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
return '//mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw' +
'/' + zoom + '/' + normalizedCoord.x + '/' +
(bound - normalizedCoord.y - 1) + '.jpg';
},
tileSize: new google.maps.Size(256, 256),
maxZoom: 9,
minZoom: 0,
radius: 1738000,
name: 'Moon'
});
map.mapTypes.set('moon', moonMapType);
map.setMapTypeId('moon');
}
// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
var y = coord.y;
var x = coord.x;
// tile range in one direction range is dependent on zoom level
// 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
var tileRange = 1 << zoom;
// don't repeat across y-axis (vertically)
if (y < 0 || y >= tileRange) {
return null;
}
// repeat across x-axis
if (x < 0 || x >= tileRange) {
x = (x % tileRange + tileRange) % tileRange;
}
return {x: x, y: y};
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap">
</script>
https://jsfiddle.net/dobleuber/319kgLh4/
It works perfect, but I would like to create the same thing with react using react-google-maps.
I looked out in the react-google-maps code but I only see getters no setters for the map props:
getMapTypeId, getStreetView, ect.
Is there any way to achieve this without modify the react-google-maps code?
Thanks in advance
use props mapTypeId="moon" in react-google-maps
I've found a better way to solve this that preserve the changes on re-render, leaving it here to anyone who comes here.
there is an onLoad function that exposes a map instance, we can use this to set mapTypeId instead of passing it as an option. In this way, if the user changes the map type later, it will preserve the changes on re-render.
<GoogleMap
onLoad={(map) => {
map.setMapTypeId('moon');
}}
/>

Trying to place two d3 line graphs inside one Ionic2 page

I am trying to have two (or more) similiar graph on one page inside an Ionic2 app. I use d3-ng2-service for wrapping the d3 types for Angular2. My problem is the following: When I try to place the two graphs in two different div elements each inside their respective custom element the drawing fails for both. When I did select the first div in the page the second graph overrides the first one, but it does get drawn.
Is there a clever way to place graphs more the the one graph? The examples always give the outer container a unique id, which is, what I try to do too:
import { Component, Input, OnInit, ElementRef } from '#angular/core';
import { D3Service, D3, Selection, ScaleLinear, ScaleTime, Axis, Line } from 'd3-ng2-service'; // <-- import the D3 Service, the type alias for the d3 variable and the Selection interface
#Component({
selector: 'd3-test-app',
templateUrl: 'd3-test-app.html',
providers: [D3Service],
})
export class D3TestAppComponent {
//Time is on x-axis, value is on y-axis
#Input('timeSeries') timeSeries: Array<{isoDate: string | Date | number | {valueOf(): number}, value: number}>;
#Input('ref') ref: string;
/* the size input defines, how the component is drawn */
#Input('size') size: string;
private d3: D3;
private margin: {top: number, right: number, bottom: number, left: number};
private width: number;
private height: number;
private d3ParentElement: Selection<any, any, any, any>; // <-- Use the Selection interface (very basic here for illustration only)
constructor(element: ElementRef,
d3Service: D3Service) { // <-- pass the D3 Service into the constructor
this.d3 = d3Service.getD3(); // <-- obtain the d3 object from the D3 Service
this.d3ParentElement = element.nativeElement;
}
ngOnInit() {
let x: ScaleTime<number, number>;
let y: ScaleLinear<number, number>;
let minDate: number;
let maxDate: number;
let minValue: number = 0;
let maxValue: number;
// set the dimensions and margins of the graph
switch (this.size) {
case "large":
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
break;
case "medium":
this.margin = {top: 20, right: 0, bottom: 20, left: 20};
//golden ratio
this.width = 420 - this.margin.left - this.margin.right;
this.height = 260 - this.margin.top - this.margin.bottom;
break;
case "small":
this.margin = {top: 2, right: 2, bottom: 3, left: 5};
this.width = 120 - this.margin.left - this.margin.right;
this.height = 80 - this.margin.top - this.margin.bottom;
break;
default:
this.margin = {top: 20, right: 20, bottom: 30, left: 50};
this.width = 640 - this.margin.left - this.margin.right;
this.height = 480 - this.margin.top - this.margin.bottom;
}
// ...
if (this.d3ParentElement !== null) {
let d3 = this.d3; // <-- for convenience use a block scope variable
//THIS FAILS...
let selector: string = '#' + this.ref + ' .graphContainer';
console.log(selector);
let svg = d3.select( selector).append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform",
"translate(" + this.margin.left + "," + this.margin.top + ")");
this.timeSeries.forEach((d) => {
d.isoDate = +d3.isoParse(d.isoDate as string);
d.value = +d.value;
if (minDate == null || minDate >= d.isoDate) {
minDate = d.isoDate as number;
}
if (maxDate == null || maxDate <= d.isoDate) {
maxDate = d.isoDate as number;
}
// if (minValue == null || minValue >= d.value) {
// minValue = d.value as number;
// }
if (maxValue == null || maxValue <= d.value) {
maxValue = d.value as number;
}
});
// TODO magic numbers to real min max
x = d3.scaleTime().domain([minDate, maxDate]).range([0,this.width]);
y = d3.scaleLinear().domain([0, maxValue]).range([this.height, 0]);
let xAxis: Axis<number | Date | {valueOf() : number;}> = d3.axisBottom(x);
let yAxis: Axis<number | {valueOf(): number;}> = d3.axisLeft(y);
let valueLine: Line<{isoDate: number; value: number}> = d3.line<{ isoDate: number; value: number }>()
.x(function (d) { return x(d.isoDate)})
.y(function (d) { return y(d.value)});
// Add the valueline path.
svg.append("path")
.data([this.timeSeries as {isoDate: number, value: number}[]])
.attr("class", "line")
.attr("d", valueLine);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + this.height + ")")
.call(xAxis)
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
}
}
myParser() : (string) => Date {
return this.d3.utcParse("%Y-%m-%dT%H:%M:%S.%LZ");
}
}
The HTML:
<div class='graphContainer'>
</div>
The HTML file where the custom component is used:
<ion-header>
<ion-navbar #dashboardNav>
<ion-title>Dashboard</ion-title>
<button ion-button menuToggle="favMenu" right>
<ion-icon name="menu"></ion-icon>
</button>
</ion-navbar>
</ion-header>
<ion-content>
<ion-item *ngFor="let entry of dashboard">
{{ entry.name }}
<d3-test-app [id]='entry.name' [timeSeries]='entry.timeSeries' [ref]='entry.name' size='medium'></d3-test-app>
</ion-item>
</ion-content>
Hard to debug without seeing a stack trace but it looks like this is failing because of how you select the element. I will base my answer on that assumption.
Querying by ID is handy when you have to look inside the DOM for a specific element and when you are sure there is only one element with that ID. Since you are inside an Angular element you already have the reference you need, it's the element itself, no need to dynamically create ID references.
I am not an expert in ng2 at all, but take a look a at how to select the raw element in Angular and choose the best approach for the framework. Say you go for something like the example shown on this answer:
constructor(public element: ElementRef) {
this.element.nativeElement // <- your direct element reference
}
NB - looks like there are various way of achieving this... not sure this is the best/correct one, but the goal is to get the ref anyway
Then simply select it via the D3 dsl like you are already doing by passing that raw reference
// At this point this.element.nativeElement
// should contain the raw element reference
if (this.d3ParentElement !== null) {
const { d3, element } = this; // <-- for convenience use block scope variables
const svg = d3.select(element.nativeElement).append("svg")...

Removing AngularJS currency filter decimal/cents

Is there a way to remove the decimal/cents from the output of a currency filter? I'm doing something like this:
<div>{{Price | currency}}</div>
Which outputs:
$1,000.00
Instead, I'd like:
$1,000
Can that be done using the currency filter? I know I can prepend a dollar sign onto a number, and I could write my own filter, but I was hoping a simple method exists with the existing currency filter.
Thank you.
Update: as of version 1.3.0 - currencyFilter: add fractionSize as optional parameter, see commit
and updated plunker
{{10 | currency:undefined:0}}
Note that it's the second parameter so you need to pass in undefined to use the current locale currency symbol
Update: Take note that this only works for currency symbols that are displayed before the number.
As of version 1.2.9 it's still hardcoded to 2 decimal places.
Here is a modified version that uses a copy of angular's formatNumber to enable 0 fractionSize for currency.
Normally this should be configurable either in the locale definition or the currencyFilter call but right now(1.0.4) it's hardcoded to 2 decimal places.
Custom filter:
myModule.filter('noFractionCurrency',
[ '$filter', '$locale',
function(filter, locale) {
var currencyFilter = filter('currency');
var formats = locale.NUMBER_FORMATS;
return function(amount, currencySymbol) {
var value = currencyFilter(amount, currencySymbol);
var sep = value.indexOf(formats.DECIMAL_SEP);
if(amount >= 0) {
return value.substring(0, sep);
}
return value.substring(0, sep) + ')';
};
} ]);
Template:
<div>{{Price | noFractionCurrency}}</div>
Example:
Demo
Update: fixed a bug when handling negative values
The question seems to be pretty old and the given answers are nice. However, there is another alternative solution which can also help (which I use in my projects).
This is working very well with currency symbols prefixing as well as suffixing the number for positive and negative values.
Custom filter:
angular.module('your-module', [])
.filter('nfcurrency', [ '$filter', '$locale', function ($filter, $locale) {
var currency = $filter('currency'), formats = $locale.NUMBER_FORMATS;
return function (amount, symbol) {
var value = currency(amount, symbol);
return value.replace(new RegExp('\\' + formats.DECIMAL_SEP + '\\d{2}'), '')
}
}])
Template:
<div>{{yourPrice| nfcurrency}}</div>
Examples for different locales:
10.00 (en-gb) -> £10
20.00 (en-us) -> $20
-10.00 (en-us) -> ($10)
30.00 (da-dk) -> 30 kr
-30.00 (da-dk) -> -30 kr
Please have a look at live demo for US dollars and Danish Krone.
Update
Please note that this workaround is good for AngularJS 1.2 and earlier releases of the library. As of AngularJS 1.3 you can use currency formatter with third parameter specifying fraction size - "Number of decimal places to round the amount to".
Note that in order to use default currency format coming from AngularJS localization, you would have to use currency symbol (second parameter) set to undefined (null or empty will NOT work). Example in demos for US dollars and Danish Krone.
Another thing that is worth considering is if you know you only have one locale or one type of currency, you could put the currency symbol before the number and then use the number filter like so (for US currency).
${{Price | number:0}}
More of a quick fix solution if you don't want to throw in a new filter and only have one currency.
It's late but might be it can help some one
{{value | currency : 'Your Symbol' : decimal points}}
So let's see some examples with output
{{10000 | currency : "" : 0}} // 10,000
{{10000 | currency : '$' : 0}} // $10,000
{{10000 | currency : '$' : 2}} // $10,000.00
{{10000 | currency : 'Rs.' : 2}} // Rs.10,000.00
{{10000 | currency : 'USD $' : 2}} // USD $10,000.00
{{10000 | currency : '#' : 3}} // #10,000.000
{{10000 | currency : 'ANYTHING: ' : 5}} // ANYTHING: 10,000.00000
See the demo
This is another similar solution, but it removes .00 decimal, but leaves any other decimal amount.
$10.00 to $10
$10.20 to $10.20
app.filter('noFractionCurrency', [ '$filter', '$locale', function(filter, locale) {
var currencyFilter = filter('currency');
var formats = locale.NUMBER_FORMATS;
return function(amount, currencySymbol) {
amount = amount ? (amount*1).toFixed(2) : 0.00;
var value = currencyFilter(amount, currencySymbol);
// split into parts
var parts = value.split(formats.DECIMAL_SEP);
var dollar = parts[0];
var cents = parts[1] || '00';
cents = cents.substring(0,2)=='00' ? cents.substring(2) : '.'+cents; // remove "00" cent amount
return dollar + cents;
};
}]);
Solution for angular version < 1.3,
if you use i18n the simplest way is:
$filter('number')(x,0) + ' ' +$locale.NUMBER_FORMATS.CURRENCY_SYM;
This way you have the number formatted with correct separators and currency symbol based on locale.
Another solution, this one removes the trailing zeros and finds the proper currency symbol for the most common currencies:
{{10.00|money:USD}} to $10
{{10.00|money:EUR}} to €10
/**
* #ngdoc filter
* #name money
* #kind function
*
* #description
* Formats a number as a currency (ie $1,234.56), removing trailing zeros and using the real currency symbol when possible. When no currency symbol is provided, default
* symbol for current locale is used.
*
* #param {number} amount Input to filter.
* #param {string=} symbol Currency symbol or identifier to be displayed.
* #returns {string} Formatted number. *
*/
app.filter('money', [ '$filter', '$locale', function (filter, locale) {
var currencyFilter = filter('currency');
var formats = locale.NUMBER_FORMATS;
var getCurrencySymbol = function (code) {
switch (code.toUpperCase()) {
case 'EUR': //Euro
return '€';
case 'USD': //Dólar americano
case 'MXN': //Peso mejicano
case 'CAD': //Dólar de Canadá
case 'AUD': //Dólar australiano
case 'NZD': //Dólar neozelandés
case 'HKD': //Dólar de Hong Kong
case 'SGD': //Dólar de Singapur
case 'ARS': //Peso argentino
return '$';
case 'CNY': //Yuan chino
case 'JPY': //Yen japonés
return '¥';
case 'GBP': //Libra esterlina
case 'GIP': //Libras de Gibraltar
return '£';
case 'BRL': //Real brasileño
return 'R$';
case 'INR': //Rupia india
return 'Rp';
case 'CHF': //Franco suizo
return 'Fr';
case 'SEK': //Corona sueca
case 'NOK': //Corona noruega
return 'kr';
case 'KPW': //Won de Corea del Norte
case 'KRW': //Won de Corea del Sur
return '₩';
default:
return code;
}
};
return function (amount, currency) {
var value;
if (currency) {
value = currencyFilter(amount, getCurrencySymbol(currency));
}
else {
value = currencyFilter(amount);
}
//Remove trailing zeros
var regex = new RegExp("\\" + formats.DECIMAL_SEP + "0+", "i");
return value.replace(regex, '');
};
} ]);
And here if you want to round up to nearest $1000: Live Demo:
var app = angular.module('angularjs-starter', []);
app.filter('noFractionRoundUpCurrency',
[ '$filter', '$locale', function(filter, locale) {
var currencyFilter = filter('currency');
var formats = locale.NUMBER_FORMATS;
return function(amount, currencySymbol) {
var value = currencyFilter(amount, currencySymbol);
var sep = value.indexOf(formats.DECIMAL_SEP);
if(amount >= 0) {
if (amount % 1000 < 500){
return '$' + (amount - (amount % 500));
} else {
return '$' + (amount - (amount % 500) + 500);
}
}
else{
if (-amount % 1000 < 500){
return '($' + (-amount - (-amount % 500)) + ')';
} else {
return '($' + (-amount - (-amount % 500) + 500)+ ')';
}
}
};
} ]);
app.controller('MainCtrl', function($scope) {
});
Exactly what I needed!
I added a conditional to just replace Angular's currency filter altogether and just use a modified version of the filter seen above by #Tom. I'm sure there are better ways to do this but it seems to work well for me thus far.
'use strict';
angular.module('your-module')
.filter('nfcurrency', [ '$filter', '$locale', function ($filter, $locale) {
var currency = $filter('currency'), formats = $locale.NUMBER_FORMATS;
return function (amount, symbol) {
var value = currency(amount, symbol), valArr = value.split(formats.DECIMAL_SEP);
if(parseInt(valArr[(valArr.length - 1)]) > 0) {
return value;
} else {
return value.replace(new RegExp('\' + formats.DECIMAL_SEP + '\d{2}'), '');
}
};
}]);
I have modified a bit the filter posted by #Liviu T. to accept currencies with symbol after the number and certain number of decimals:
app.filter('noFractionCurrency',
[ '$filter', '$locale', function(filter, locale) {
var currencyFilter = filter('currency');
var formats = locale.NUMBER_FORMATS;
return function(amount, num, currencySymbol) {
if (num===0) num = -1;
var value = currencyFilter(amount, currencySymbol);
var sep = value.indexOf(formats.DECIMAL_SEP)+1;
var symbol = '';
if (sep<value.indexOf(formats.CURRENCY_SYM)) symbol = ' '+formats.CURRENCY_SYM;
return value.substring(0, sep+num)+symbol;
};
} ]);
For example:
{{10.234 | noFractionCurrency:0}}
{{10.55555 | noFractionCurrency:2}}
Outputs:
$10
$10.56
Demo
If you'd use angular-i18n (bower install angular-i18n), you could use a decorator to change the defaults in the locale files, like so:
$provide.decorator('$locale', ['$delegate',
function ($delegate) {
$delegate.NUMBER_FORMATS.PATTERNS[1].maxFrac = 0;
$delegate.NUMBER_FORMATS.PATTERNS[1].minFrac = 0;
return $delegate;
}]);
Note that this would apply to all currency filter uses in your code.
In Angular 4+
{{totalCost | currency : 'USD' : 'symbol' : '1.0-0' }}

Resources