How to internationalize your Angular application?

Yesterday, we touched on how to change the locale of our Angular applications. The next step is to fully internationalize our apps by translating all text into different languages.

While Angular has its localization implementation to internationalize applications, the most popular library used for such a task is ngx-translate.

Why not use Angular’s internal implementation? The main reason is that Angular builds one version of your application per locale. As a result, if you have to support 20 different locales, your Angular build would output 20 different applications. This means that changing the site would result in a complete reload of the application. This also means that changing a translation requires a complete rebuild and redeploy of the application.

ngx-translate is much more flexible because it relies on pipes, directives, and services to translate our text at runtime instead of build-time. Here are some examples:

With TranslateService:

With TranslatePipe:

With TranslateDirective:

Translations come from specific files where translation keys ("HELLO" in my example) are mapped to actual values in each language. ngx-translate supports different formats out of the box (such as JSON and PO).

Here is an example of English translations stored in en.json:

And the same translations in French in fr.json:

All we have to do is indicate where the translation files are located and ngx-translate will download them as needed based on the locale.

Defining which languages are supported as well as which one to use by default can all be done with the TranslateService from the library:

You can see a live example of ngx-translate in Stackblitz here. If you want to try it, you can use my ngx-translate step-by-step tutorial.

How to change the locale of your Angular application?

A locale is a combination of country and language, such as fr-CA for French spoken in Canada or en-US for English spoken in the US. Locales matter when you want to create an application that works all around the globe.

For instance, let’s take the following sentence in American English for the US:

On 04/01/2023, the price of this item was $2,056.23

The same sentence in French for France would be:

Le 01/04/2023, le prix de cet article était 2 056,23 €

As you can see, everything is different. Currency, number format, date format, and of course, language. Knowing all of these differences between locales would be a complex task. Fortunately for us, Angular knows about most locales, and all pipes and formatting functions use that locale to format dates/numbers/currencies correctly.

To support different locales, we have to import all of them in main.ts:

Then you can configure your providers to set a default LOCALE_ID:

And just like that, the expression {{ 2056.23 | number }} would display 2 056,23 instead of 2,056.23 (code example here). So our pipes will use that default locale from now on.

Named router outlets

Using the Angular Router is one of the best ways to emulate multiple pages/screens in an Angular application. However, we might sometimes want even more flexibility by having the equivalent of “sub-pages” where sibling elements on a web page can display dynamic content based on the current URL.

Something like this where three different divs can display different components without a parent/child relationship:

Angular allows us to have named router outlets to support such scenarios. We can define several sibling outlets by giving them distinctive names like so:

Then, in our router config, we can define which component goes in which outlet for which path:

Finally, when routing a component to an outlet, we have to repeat some of that config in the form of a rather lengthy command:

The above code will load two different components in two different named outlets. You can see a live example on Stackblitz here.

Using a resolver function or a title strategy to change the page title

Before we get into today’s topic: The Angular Team’s Request for Comments (RFC) for Angular Signals is now open to the public. Feel free to take a look and give your feedback.

Yesterday, we saw how to change an application’s title using the router config. Today, let’s cover more complex scenarios when we need a dynamic title set depending on other factors.

Using a resolver function

First, we might need some processing using a function to extract information from a service or API. In that case, we can use a resolver function that gets that data and returns it:

Note that the signature of that function allows us to return a string, an Observable, or a Promise. That way, we can make an API request to fetch some extra data if needed. For instance, in the above example, getUsername() returns an Observable (code here). This is another excellent example of using the inject function we covered earlier in this newsletter.

Using a title strategy service

For more complex scenarios, Angular allows us to create a custom TitleStrategy. Such strategy takes the form of a service that has to implement an updateTitle method:

Here is an example where I add a prefix to the title, then use the default title from the title property in the router config. Note that we could easily inject other services and perform async operations before setting the new title if needed:

Such title strategy service has to be provided as the default title strategy in our dependency injection configuration as follows:

You can see that example in action here on Stackblitz.

Changing the page title using Angular Router

This is a quick post to showcase a recent addition to the framework that is useful and very simple to implement. We can now have a title property in every route config:

The provided title is set as the page title (in your browser tab) when the user navigates from one page to the next. Just like that:

You can see a code example here on Stackblitz with a live version here. I used standalone components with the new provideRouter function just for the fun of it:

Note that there is also a Title service that can be used if you need to change that title dynamically later. Here’s a short tutorial of mine on that topic: How to change the page title and meta tags with Angular?

ngOnInit vs. constructor in Angular

Today’s post is all about clarifying when to use a constructor and when to use ngOnInit to initialize data in our Angular components.

From a chronological standpoint, the constructor is called first. Then, once all component inputs are initialized, Angular is going to call ngOnInit.

From that definition, it makes sense to use ngOnInit only when your initialization code needs a @Input value to be set. Something along those lines:

And that’s the only rule. Some might be surprised to read this, but you could initialize everything in your constructor if your component has no inputs. There is no performance impact in doing one or the other.

I would argue that triggering an HTTP request in the constructor is better as it’s going to happen sooner (we’re talking milliseconds, though) than if we do it in ngOnInit.

As a result, in the absence of any official recommendation in the Angular style guide, you can choose one or the other.

Resolver function for the Angular Router

The last router function of our guard series is resolve. It is the replacement for class-based resolvers. A resolver function runs after all guards have run and before the destination component is rendered. Such a function is typically used to preload component data before rendering it on the screen.

For instance, in this example:

heroResolver is a function that returns either some data or a Promise or an Observable of that data. Such data will be stored in the hero property of a data object returned by an Observable from the ActivatedRoute service as follows:

Of course, we could resolve multiple different pieces of data using the following syntax:

resolve: {user: userResolver, session: sessionResolver},Code language: JavaScript (javascript)

A resolver function can use the inject function to use services and resolve data as follows:

export const userResolver: ResolveFn<User> = () => { 
    return inject(UserService).getCurrentUser(); 
 };Code language: TypeScript (typescript)

canMatch Router Guard

After introducing guards and the most common guard functions (canActivate, canActivateChild, and canDeactivate), we will cover one slightly different guard that can run when trying to access a route.

canMatch was introduced in Angular 15.1. When canMatch returns false, Angular will continue looking at other routes that would match that path instead of preventing any navigation.

Let’s consider the following example:

With the above code, both routes are configured for the /user path, but if the user is an admin user, they’ll land on an AdminDetails component, and if they’re a regular user, they’ll land on a UserDetails component.

This approach can be helpful when working with multiple user roles that display different components.

Route guards – canActivate, canActivateChild, canDeactivate

Yesterday, we introduced how route guards can control whether or not a user can navigate to a new URL in our application.

The main guard to achieve that behavior is canActivate. This guard will run every time the user tries to access a path and only allow the user to proceed if the guard function returns true.

Here’s an example that’s going to check if the user is logged in before proceeding:

If we want to redirect the user to a login component when they’re not logged in, we can do so by using the Router service and returning a UrlTree object as follows:

Note how the inject function is used in all these guards to access any service we need.

canActivate works well when navigating to components. If we want to prevent the lazy-loading of a child route or prevent access to any path of the child route, then canActivateChild is what we need instead of canActivate.

Finally, if you want to prevent the user from navigating away from the current path/component, you can implement the canDeactivate guard. A typical example of such a use case is when working with complex forms where we want to make sure the user has saved their progress before navigating away from the current screen/component:

Another exciting feature is that we can access the current component itself instead of a service, which is especially useful for canDeactivate:

If you like those new framework features, I covered some more utility router functions released with Angular 14+ in this post.

How to use Angular route guards?

The Angular router has 5 different route guards that can be used to control user navigation in the browser.

Such guards can be used in scenarios such as:

  • Preventing users from navigating to parts of an application without authorization
  • Preventing users from leaving a form before it is saved or completed
  • Making sure the user is logged in before proceeding to the next page

A guard is a function (class-based guards are deprecated as of Angular 15.2) that can return three different values:

  • true if we want to allow the user to proceed (then the navigation happens)
  • false if we want to prevent the user from proceeding (then the navigation doesn’t happen)
  • A UrlTree if we want to redirect the user to another component (usually a login component or an error component)

Sometimes, we want to ask a server about the user, which means that our guard is going to do asynchronous work. That’s fine because guards can return an Observable or a Promise of the above values, which means that a guard can return any of the six following types:

Any route can be protected by one or more guards. In the router config, we define an array of guards for each route as follows:

The above code means that in order to access the path /team/:id, a user has to satisfy the requirement of canActivateTeam, a guard function that returns one of the values documented above.