Angular: Dynamic Import of Locales

It would be great if we can dynamically import the language when needed. All sounds good in theory. Let's see if it also works in practice.

This article was written while I was trying to get the imports working. I have not censored the errors I ran into in the hope that it will help someone who runs into the same issues.

At the end, I have a link to example code on GitHub.

The Goal

  • Remove import statements for locales.
  • Replace switch statement with dynamic import.
  • Lazy loading.

We want to remove the imports of locales:

import { registerLocaleData } from '@angular/common';
import localeNorwegian from '@angular/common/locales/nb';
import localeSwedish from '@angular/common/locales/sv';

And we want to replace the switch statement that registers the locale data with the dynamic import.

switch (culture) {
  case 'nb-NO': {
  case 'sv-SE': {


Before we start, we need to open tsconfig.ts and change module type to esNext since dynamic imports are not in the official ES spec yet. Otherwise, we will get this error:

Dynamic import is only supported when ‘ — module’ flag is ‘commonjs’ or ‘esNext’
esNext is a place holder for features that are on the standard track but is not in an official ES spec yet. e.g. import.meta and import()expressions. As TC39 adds these features to a versioned spec, new --module ES20** will be added to reflect that.

Dynamic Import

The Dynamic Imports allow us to load our code on demand asynchronously.

So now let's follow the article where it says:

import('xlsx').then(xlsx => {

And change our code to:

import('@angular/common/locales/nb').then(lang => registerLocaleData(lang));

But we get an error:

Uncaught (in promise): TypeError: Cannot read property ‘toLowerCase’ of undefined at Object.registerLocaleData

It seems we need to use the default property.

import('@angular/common/locales/nb').then(lang =>

No errors! But I wonder why we don't get the default by default?


If you have some code right after the import that updates the values that use you might get this error:

Missing locale data for the locale "nb-NO".

This is because your module did not import before the pipe tried to use it.

Since fetching an ECMAScript module on demand is an asynchronous operation, an import() expression always returns a promise.

So, we have to wait until the import is done. To make the code clearer let's move the importing to a function.

And now we can use that method with some callback code:

this.localeInitializer().then(() => {
  // Update values here
Callbacks added with then() will be called after the success or failure of the asynchronous operation.

If we build this code we can see that we are getting a new chunk:

So we can see that we are now getting that language we added in the dynamic import in our build.

More Languages

So this is great and all but we do not want to hard-code all the languages that we import. Instead, let's use the characters before the hyphen from our culture string to get our localeId.

const localeId = culture.substring(0, culture.indexOf('-'));

And then let's refactor localeInitializer() so we can pass it that id.

Now we get a ton of these warnings:

We are trying to import too many files, some of which break the build. If we look in the folder node_modules\\@angular\\common\\locales we see that every language has one JavaScript file and one TypeScript definition file. Since we only need the .js files, we can change our code to reflect it:


I'm using a Template Literal, and now the build works again.

We get lots of chunks now.

It's bundling all the language files in the locale folder to individual chunks. This way we can lazy load the language we need.

Magic Comments

In my case, I only have two languages so bundling all of them seems like overkill. Since we are using the CLI, I thought that perhaps there is no way actually to define which languages we want to bundle.

But there is some magic we can do!

And they are called webpack magic comments. By utilizing this magic comment we can send in the languages we want to bundle to webpack:

/* webpackInclude: /(nb|sv)\.js$/ */

With this code, our function is complete and looks like this:

 localeInitializer(localeId: string): Promise<any> {
    return import(
      /* webpackInclude: /(nb|sv)\.js$/ */
      ).then(module => registerLocaleData(module.default)

You can also exclude files if that is more convenient:

/* webpackExclude: /(languages|here)\.js$/ */

And now when we recompile we only get the extra chunks we want.

For more options you can check the documentation.

Lazy Loading

We have our language chunks bundled and ready but let's see if they are lazy loaded by using the network tab in the developer tools.

Yes, we can see that when we start the application that no language chunks are loaded. And when we press the buttons for Norway or Sweden, the chunk for the corresponding country is downloaded.

That's great! So with researchgrit and occasional magic, we were able to do what we set out to do. I was not sure that this article would end happily, but I'm glad it did.

You can download the code from GitHub if you want to try it out.

Thanks for reading. If you liked this post, share it with all of your programming buddies!

Further reading

☞ Angular 8 (formerly Angular 2) - The Complete Guide

☞ Angular & NodeJS - The MEAN Stack Guide

☞ The Complete Node.js Developer Course (3rd Edition)

☞ The Web Developer Bootcamp

☞ Best 50 Angular Interview Questions for Frontend Developers in 2019

☞ How to build a CRUD Web App with Angular 8.0

☞ React vs Angular: An In-depth Comparison

☞ React vs Angular vs Vue.js by Example

#angular #angular-js #javascript #typescript #web-development

Angular: Dynamic Import of Locales
3 Likes27.00 GEEK