I have an Angular frontend with hundreds of html input fields, which trigger calculation of a result upon typing.
<input type="text" [(ngModel)]="myModel.myAttribute" (input)="calculate()">
My problem is that the input needs to be able to handle decimals, but the calculation breaks (result displays as NaN) when a number using ‘,’ (comma) as decimal separator is typed/used.
I have registerLocaleData() and LOCALE_ID in providers:[] in app.module which I thought would be enough (this was originally for app-wide localized date formatting, but the "senior" devs at the office say this should change number formatting as well – though this is clearly ‘in theory’ since they don’t have any projects where they had to take decimals as input – and in my case it doesn’t even change the display of numbers with decimals to use comma as separator).
This does however change the default display in the other dev’s projects, and they don’t understand why it wouldn’t at least [do that] in my case. They’ve looked over my code, and they can’t find anything that would interfere with this behavior (though I have yet to see their projects, so, once again, from my perspective, this is highly theoretical).
Am I (and the other dev’s at my office) wrong for thinking registerLocalData() and LOCALE_ID should affect numbers as well?
Are there any other ways to apply local number formatting [on a higher level] to "give it" to all components?
Code examples (copy/pasted from my project, but with renamed variables, with most of them omitted (since they’re just variants of the same thing) for simplicity, and really just to "prove" that I don’t have any "special" or "weird" code that should interfere with any form of localisation):
App module
import { NgModule, APP_INITIALIZER, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeSe from '@angular/common/locales/sv';
registerLocaleData(localeSe, 'sv-se');
@NgModule({
declarations: [
AppComponent,
ParentComponent,
ChildComponent,
OtherChildComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
],
exports: [
AppRoutingModule,
HttpClientModule,
],
providers: [
httpInterceptorProviders,
{ provide: LOCALE_ID, useValue: "sv-se" },
],
bootstrap: [AppComponent],
})
export class AppModule { }
Parent component
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { ChildComponent } from '../child/child.component';
import { OtherChildComponent } from '../otherChild/otherChild.component';
import { ParentObject } from '../models';
@Component({
selector: 'parent',
templateUrl: './parent.component.html',
styleUrls: ['./parent.component.css'],
})
export class HarvestComponent implements OnInit {
@Input() parentId!: number;
@ViewChild(ChildComponent) childComponent!: ChildComponent;
@ViewChild(OtherChildComponent) otherChildComponent!: OtherChildComponent;
parentObject!: ParentObject
constructor(private route: ActivatedRoute, private parentSvc: ParentService) {
}
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
if (params['internalReference'] != undefined) {
this.parentId= params['internalReference'];
console.log("n HARVEST ID HAS BEEN SET VIA QUERY PARAMSn");
}
if (this.parentId)
this.getParentObject().then((object) => {
this.parentObject = object;
})
});
Child component
import { HttpErrorResponse } from "@angular/common/http";
import { Component, Input, OnInit } from "@angular/core";
import { Subscription } from "rxjs";
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['../child.component.css']
})
export class ChildComponent implements OnInit {
@Input() parentObjectId!: number;
@Input() otherChildObject!: OtherChildObject;
@Input() editDisabled: boolean = true;
mySub!: Subscription;
myObject!: MyObject;
constructor(private childSvc: ChildService, private toastr: ToastrService) {
}
ngOnInit(): void {
if (this.parentObjectId && this.parentObjectId != 0)
this.getChildObject(this.parentObjectId ).then((object) => {
this.myObject= object;
this.calculateDiff();
}).catch((error) => {
this.toastr.error("An error occurred!");
console.log(error.message);
});
}
calculateDiff() {
var var1 = this.myObject.oneAttr as number;
var var2 = this.otherChildObject.anotherAttr as number;
var diff = ((var1 - var2 ) / var2) * 100;
this.myObject.diffAttr = diff;
}
}
My index.html does have a lang tag
<html lang="sv">
For certain result-fields I use number pipe to control how many decimals to show, which for some reason enables "correct" display of the result, with comma as separator.
<input type="text" [disabled]="editDisabled" [ngModel]="myModel.myAttribute | number : '1.1-1'">
Though I would prefer not to have to use this type of solution (like a pipe for every ) more than I already have, since I am dealing with over 200 different input fields – but it also doesn’t seem to change the actual ability to input a comma as separator, so I guess that is a moot detail anyway.
I’ve looked around for solutions online, and I’ve found many (potential ones), but none of them work – at least not without writing custom functions/pipes to change the input decimal separator from ‘,’ to ‘.’ which, once again, I would love to not have to do, since it would have to be applied (in one way or another) to basically every one of the hundreds of fields.
I’ve tested with the same results in FireFox, Chrome, and Edge…
Is there any light to be shed on this issue at all?
Appreciate any and all input.
Thank you.
2
Answers
DecimalPipe
is already considering your app’sLOCALE_ID
, so you do not need to specify how to format, get rid of digitsInfo.But you should not use it in the input. To take
1,23
for input and store it as1.23
,ngx-mask
will help you. With it, you use your input as follows:There are many options to
mask
..2
defines that input will only accept 2 fractional digits.You can configure
ngx-mask
and specifythousandSeparator
,decimalMarker
and much more only once. If you really do not want to search and replace those inputs to add mask directive, you can also wrapngx-mask
in custom directive you create, and set that directive’s selector in a manner that it will trigger every input you need to mask. For example it might beinput[type=text]
, alleviating the need to addmask
to 200 inputs. But editing 200 inputs might be easier!My understanding is that you try to have the user input a number. In the html element I can see that you have configured the type property to be a "text", but you should configure it to be a "number" input element. (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number) It helps with parsing the users input and won’t let him input non-numbers. Since the default input type of a "text" input is a string type, you will get a NaN exception like you described. Hope it helps a little. Let me know if you need further help