Reputation: 21
I have .Net Web API which returns data received from a third party API to my user facing Angular application. When API returns a big number like 999,999,999,999,999.99, Angular Changes it to 10,000,000,000,000,000. I don’t want to round this number as it’s sensitive data.
There is a workaround in my mind to resolve this issue and retain the decimal place for such huge numbers. The workaround is to convert the numbers to string before sending the data back to the Angular app. Two ways to achieve this:
Use some regular expression and make all number fields as string by placing quotes around the number.
Currently, in the .NET API I am reading the response of the third party API as string and put it into a ContentString before sending it to the Angular app. Instead of reading the content as String, I can define a model and within that model I can define all the number type fields as String.
I would like to know which one of these two approaches is better. I will really appreciate if someone can share some proper way to achieve this.
Upvotes: 2
Views: 2451
Reputation: 1851
As others pointed out, this is an issue with JS rather than Angular. Numbers in JS are double precision floating point numbers, meaning you loose precision for high numbers or numbers with many decimal places. As you mentioned you're already using bignumber.js, you seem to be aware of this fact.
Specifically, when you're performing an HTTP request with response type json
, you'll usually never actually get a hold of the "real" value sent in the json body. The JSON string will automatically be parsed to a JS object, already loosing precision at this point.
Luckily, there's a way to prevent this. In Angular, you can use an HttpInterceptor that changes the response type from json
to text
and after receiving the response, manually parses it. As at this point, JS did not do anything to the response and it's just the plain string that your API sent to the frontend, no precision is lost at this point.
For the JSON parsing and turning all number
s to BigNumber
s, I use the JSONBigNumber library for convenience, but I'm sure this is also easily possible using standard utility.
The code for the described interceptor looks something like this:
@Injectable()
export class BigNumberInterceptor implements HttpInterceptor {
intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return httpRequest.responseType === "json"
? this.handleJsonResponses(httpRequest, next)
: next.handle(httpRequest);
}
private handleJsonResponses(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next
.handle(httpRequest.clone({ responseType: "text" }))
.pipe(
map(response => this.parseResponse(response)),
catchError(response => this.handleErrorResponse(response))
);
}
private parseResponse(response: HttpEvent<any>): HttpEvent<any> {
return response instanceof HttpResponse
? response.clone({ body: this.parseJson(response.body) })
: response;
}
private handleErrorResponse(response: any): Observable<HttpEvent<any>> {
if (response instanceof HttpErrorResponse) {
if (response.error == null) {
throw response;
} else {
throw new HttpErrorResponse({
error: this.parseJson(response.error),
headers: response.headers,
status: response.status,
statusText: response.statusText,
url: response.url ?? undefined
});
}
} else {
throw response;
}
}
private parseJson(jsonBody: string) {
if (!jsonBody) {
return jsonBody;
} else {
try {
return JSONBigNumber.parse(jsonBody);
} catch (e) {
return jsonBody;
}
}
}
}
It looks like a lot of code for a simple task, but that's just because HttpErrorResponse
s need to be handled separately, as they of course also contain JSON responses. As the interceptor changes the response type to text
, this also applies to error responses. So the parsing needs to be done manually here as well.
You also need to provide this interceptor for Angular's HTTP_INTERCEPTORS
injection token, so Angular knows it should use it. You can add the provider in your AppModule
like this:
@NgModule({
// ...
providers: [
// ...
{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: BigNumberInterceptor
}
]
})
export class AppModule {
// ...
}
Upvotes: 0
Reputation: 166
I think you can use BigJS:
var n = new Big('999999999999999.99');
Upvotes: 1
Reputation: 1521
This is a JavaScript issue as opposed to an Angular specific issue. JavaScript numbers are always stored as double precision floating point numbers, following the international IEEE 754 standard.
Further reading:
https://www.w3schools.com/js/js_numbers.asp
Examples:
(function() {
//1000000000000000
const a = 999999999999999.99
//99999999999999.98
const b = 99999999999999.99
//999999999999999
const c = 999999999999999
//'999999999999999.99'
const d = '999999999999999.99'
console.log(a);
console.log(b);
console.log(c);
console.log(d);
}
)();
I think you're going to be struggling to store a number greater than 999999999999999 without issues. Best to store as string perhaps.
Upvotes: 0