Issue
We migrated an Angular application from version 4 to 15 recently and are running into an issue when using the DecimalPipe for formatting numbers. Our application is configured to use the navigator.language
as the LOCALE_ID
of the application. The result would be that numbers are correctly formatted based on the user's locale.
After upgrading to Angular 15, this longer works since now it is required to register all locales you want to support by the application. If you don't do this, the numbers are not rendered and an error is logged to console stating that the locale information is not found.
It is not an option for us to register known locales since the application is used world wide and thus it should basically support any locale.
The question is, is it possible to load locale information at runtime (e.g in the AppModule
) ?
If possible, we want to stick with the default Angular l10n support instead of using a third party library.
Solution
I don't know how desperate you are for a solution, but if you're only using navigator.language
for number formatting, then you could write a new implementation of the number pipe.
const NAVIGATOR_LOCALE_ID = new InjectionToken<string>(
'navigator locale id',
{
providedIn: 'root',
factory: () => window !== undefined ? window.navigator.language : 'en-US'
}
);
@Pipe({
name: 'number',
standalone: true,
})
export class NavigatorLocaleDecimalPipe implements PipeTransform {
private _locale = inject(NAVIGATOR_LOCALE_ID)
transform(value: number|string, digitsInfo?: string, locale?: string): string|null;
transform(value: null|undefined, digitsInfo?: string, locale?: string): null;
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string|null;
/**
* @param value The value to be formatted.
* @param digitsInfo Sets digit and decimal representation.
* @param locale Specifies what locale format rules to use.
*/
transform(value: number|string|null|undefined, digitsInfo?: string, locale?: string): string | null {
if (value == null || value == '') {
return null;
}
locale = locale || this._locale;
const formatter = new Intl.NumberFormat(locale, parseDigitsInfo(digitsInfo));
return formatter.format(typeof value === 'string' ? parseFloat(value) : value);
/* returns NumberFormatOptions that correspond to digitsInfo */
function parseDigitsInfo(digitsInfo?: string): Intl.NumberFormatOptions {
if (digitsInfo) {
const parsed = /^\s*(\d+)\.(\d+)-(\d+)\s*$/.exec(digitsInfo);
if (parsed) {
return {
minimumIntegerDigits: parseInt(parsed[1]),
minimumFractionDigits: parseInt(parsed[2]),
maximumFractionDigits: parseInt(parsed[3])
}
}
}
return {};
}
}
}
The first piece is an injection token for the navigator locale called NAVIGATOR_LOCALE_ID. It will return a string for the navigator language just like LOCALE_ID. (The window !== undefined
check is in case you're using this in a context where window is unavailable.)
Next a pipe is created called NavigatorLocaleDecimalPipe. It has the following features:
- It injects the newly created injection token NAVIGATOR_LOCAL_ID so the locale doesn't have to passed as a parameter in the pipe.
- The formatting is performed with the Intl.NumberFormat class. This should have very wide support (node, deno, bun, etc...).
- A simple function is created to convert the digitsInfo string into NumberFormatOptions.
*It might be a good idea to "memoize" and reuse formatters since there will likely be a lot of the same pipe arguments.
Make sure the pipe is imported after CommonModule if you keep the pipe's name as number and both are imported into the same module or component. Whichever is imported second seems to take precedence.
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, NavigatorLocaleDecimalPipe],
template: `
<div>This is {{ 50000 | number: '1.2-2' }}</div>
`,
})
export class App {}
Answered By - Daniel Gimenez
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.