Custom Angular Pipes and Dynamic Locale

Custom Angular Pipes and Dynamic Locale

Support more than one date format with custom pipes.

In my article Get Started with your first Angular Library I went through how to create an Angular library. This time I will start adding some useful things to it and the first thing I am going to add to it is a couple of custom pipes.

The DatePipe in Angular is great, but if you want to be able to support more than one language in your application you might want to implement your own pipe. In this article, I will show how to register your locales, create your custom pipes and then we will add them to our library.

If we want to just change from the default en-US locale we can do it in our app.module.ts.

@NgModule({
  providers: [
    { provide: LOCALE_ID, useValue: 'de-DE' }    
  ]  
})

You could also be more dynamic and get your locale from a service by using the FactoryProvider.

Remember to register the locale you are going to use with registerLocaleData.

{
  provide: LOCALE_ID,
  deps: [SessionService],
  useFactory: (sessionService) => sessionService.getLocale()
}

But what if we want to be able to change languages in our application? Then we can’t just send the language to our bootstrapping module once and be done with it.

The built-in date and number pipes do accept locale as an argument so we could, in theory, send in a locale that we save in our session and are able to change. And for a smaller app this could be a viable solution.

What I have found works best so far is to create custom pipes that use a locale to format our dates and number. Angular recently (Angular 5?) provided us with formatDate and formatNumber. So it is quite easy now to create our own custom pipes by using these functions.

Implementing Pipes and Session Service

Let's start coding already! An example of how the pipe can look is as follows:

/**
  * Usage: dateString | localDate:'format'
 **/

import { Pipe, PipeTransform } from '@angular/core';
import { formatDate } from '@angular/common';
import { SessionService } from '../services/session.service';

@Pipe({
    name: 'localDate'
})
export class LocalDatePipe implements PipeTransform {

    constructor(private session: SessionService) { }

    transform(value: any, format: string) {

        if (!value) { return ''; }
        if (!format) { format = 'shortDate'; }

        return formatDate(value, format, this.session.locale);       
    }
}

You will also need to register the locale with Angular. Let's create a session service where registerCulture gets called with the culture we want to use:

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

@Injectable({ providedIn: 'root' })
export class SessionService {

    private _locale: string;

    set locale(value: string) {
        this._locale = value;
    }
    get locale(): string {
        return this._locale || 'en-US';
    }

    registerCulture(culture: string) {
        if (!culture) {
            return;
        }
        this.locale = culture;

        // Register locale data since only the en-US locale data comes with Angular
        switch (culture) {
            case 'nb-NO': {
                registerLocaleData(localeNorwegian);
                break;
            }
            case 'sv-SE': {
                registerLocaleData(localeSwedish);
                break;
            }
        }
    }
}

Another great thing to do that you might not think about is formatting your numbers. This is very similar to the date pipe:

/**
  * Usage: number | localNumber:'format'
  * If no format is provided 2 decimals will be used.
 **/

import { Pipe, PipeTransform } from '@angular/core';
import { formatNumber } from '@angular/common';
import { SessionService } from '../services/session.service';

@Pipe({
    name: 'localNumber',
})
export class LocalNumberPipe implements PipeTransform {

    constructor(private session: SessionService) { }

    transform(value: any, format: string) {
        if (value == null) { return ''; } // !value would also react to zeros.
        if (!format) { format = '.2-2'; }

        return formatNumber(value, this.session.locale, format);
    }
}

Like this blog post? Share it on Twitter! 🐦

Updating the library

Now we can update the library with our new code and package a new version.

  1. Add local-date.pipe.ts and local-number.pipe.ts in the pipes folder.

  2. Add session.service.ts in the services folder.

  3. Export all three added files from public-api.ts.

  4. Add the pipes to the declarations and exports section in lib-shared.module.ts.

Let's update the version, build and pack our lib-shared to the new version of our library lib-shared-0.0.2.tgz.

libraries> npm version patch --prefix projects/lib-shared
libraries> ng build lib-shared
libraries\dist\lib-shared>* npm pack

Let's now go over to our other project and install the updated package. Change your version of lib-shared to 0.0.2 and npm install it.

Testing the pipes

Now let's write some code to test our library:

import { Component } from '@angular/core';
import { SessionService } from 'lib-shared';

@Component({
  selector: 'app-root',
  template: `
    <p>ngDate: {{today | date:'shortDate'}}</p>
    <p>localDate: {{today | localDate}}</p>
    <p>ngNumber: {{val | number}}</p>
    <p>localNumber: {{val | localNumber}}</p>
    <button (click)="norway()">Norway</button>
    <button (click)="sweden()">Sweden</button>
  `
})
export class AppComponent {

  today = new Date();
  val = 123.45;

  constructor(private session: SessionService) {}

  norway() {
    this.session.registerCulture('nb-NO');
    this.refreshValues();
  }

  sweden() {
    this.session.registerCulture('sv-SE');
    this.refreshValues();
  }

  private refreshValues() {
    this.today = new Date();
    this.val++;
  }
}

This will let you choose between the Swedish and Norwegian locales. You can press the Norway/Sweden buttons to see how the app dynamically changes the locale.

And that’s it!

Now you can easily import this library to your projects whenever you need to add some local flavor to your app.

Call to Action

I always enjoy feedback so please 👏, 📝 and tweet 🐦. Follow me on Twitter and Medium for blog updates.

Update: I have released a follow-up article on this topic called ‘Dynamic Import of Locales in Angular’.

Did you find this article valuable?

Support Michael Karén by becoming a sponsor. Any amount is appreciated!