Lesson 07-Angular Pipes and Internationalization

Understanding Pipes and Their Usage

Angular pipes are a mechanism for transforming data, taking an input value and returning a new value. Pipes are primarily used in templates to simplify data processing logic in the view layer, making templates cleaner and more readable.

Pipe Types

Angular provides several built-in pipes and supports custom pipes. Common built-in pipes include:

  • DatePipe – Formats dates.
  • DecimalPipe – Formats numbers.
  • CurrencyPipe – Formats currency.
  • PercentPipe – Formats percentages.
  • UpperCasePipe and LowerCasePipe – Converts strings to uppercase or lowercase.
  • JsonPipe – Converts objects to JSON strings.
  • SlicePipe – Extracts a portion of an array or string.

Using Built-In Pipes

Consider the following component:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <h1>{{ title | uppercase }}</h1>
    <p>Today is {{ today | date:'full' }}</p>
  `
})
export class AppComponent {
  title = 'My App';
  today = new Date();
}

In this example, the uppercase pipe transforms the title property to all uppercase, and the date pipe formats the today property into a full date format.

Custom Pipes

Custom pipes require implementing the PipeTransform interface, which includes a transform method. Here’s an example of a custom pipe:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'reverse'
})
export class ReversePipe implements PipeTransform {
  transform(value: string): string {
    return value.split('').reverse().join('');
  }
}

Register the pipe in the module:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ReversePipe } from './reverse.pipe';

@NgModule({
  declarations: [
    AppComponent,
    ReversePipe
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Use the custom pipe in the template:

<p>Reversed text: {{ "Hello World" | reverse }}</p>

Pipe Parameters

Pipes can accept parameters, such as the date pipe accepting different format strings:

<p>Today is {{ today | date:'yyyy-MM-dd' }}</p>

Pipe Chaining

Pipes can be chained, for example, reversing a string and then converting it to uppercase:

<p>{{ "Hello World" | reverse | uppercase }}</p>

Asynchronous Pipe Example

Create an asynchronous pipe:

import { Pipe, PipeTransform } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Pipe({
  name: 'asyncMap',
  pure: false // Set to false to make the pipe impure, recalculating results on every call
})
export class AsyncMapPipe implements PipeTransform {
  transform<T, R>(obs: Observable<T>, transformer: (data: T) => R): Observable<R> {
    return obs.pipe(map(transformer));
  }
}

Use the pipe in a component:

import { Component } from '@angular/core';
import { of } from 'rxjs';

@Component({
  selector: 'app-root',
  template: `
    <div *ngFor="let item of items$ | asyncMap: (item) => item.toUpperCase()">
      {{ item }}
    </div>
  `
})
export class AppComponent {
  items$ = of('apple', 'banana', 'cherry');
}

In this example, the AsyncMapPipe takes an Observable and a transformer function, returning a new Observable. This allows handling asynchronous data streams directly in the template.

Pipe Performance Optimization

While pipes enhance template readability and maintainability, improper use can lead to performance issues, especially with frequent calls or large datasets.

  • Avoid Complex Pipes in Loops: Minimize using complex or computationally intensive pipes in *ngFor loops, as they recalculate for each iteration.
  • Use Pure Pipes: By default, pipes are pure, meaning they don’t re-execute if inputs haven’t changed, improving performance by avoiding redundant calculations.
  • Performance Monitoring: Use Angular’s performance tools, such as Chrome DevTools, to check if pipes cause unnecessary re-rendering.

Internationalization (i18n) Support and Configuration

Angular’s internationalization (i18n) support enables developers to create multilingual applications, crucial for global websites and apps. Angular offers built-in i18n tools via Angular CLI and supports third-party libraries like ngx-translate.

Angular CLI’s Internationalization Support

Angular CLI provides a complete i18n workflow, including extracting translation strings, creating translation files, compiling, and testing. Steps for using Angular CLI for i18n:

Extract Translation Strings

Use the ng xi18n command to extract translation strings from templates into an .xlf file:

ng xi18n --output-path=./src/locale

Create Translation Files

Create an .xlf file for each language, typically named messages.[lang].xlf, where [lang] is the language code (e.g., en or fr).

Configure angular.json

Add or update the i18n configuration in angular.json to specify the source language and translation file paths for other languages:

"i18n": {
  "sourceLocale": "en",
  "locales": {
    "fr": {
      "translation": "./src/locale/messages.fr.xlf"
    },
    "es": {
      "translation": "./src/locale/messages.es.xlf"
    }
  }
}

Build Multilingual Versions

Build different language versions using --i18n-file and --i18n-format parameters:

ng build --configuration=production --i18n-file=./src/locale/messages.fr.xlf --i18n-format=xlf --locale=fr

Use Translation Service

Angular provides the TranslateService, which can be injected and used in components:

import { TranslateService } from '@ngx-translate/core';

constructor(private translate: TranslateService) {
  this.translate.setDefaultLang('en');
}

changeLanguage(language: string) {
  this.translate.use(language);
}

Use Translation Keys in Templates:

<h1>{{ 'HELLO_WORLD' | translate }}</h1>

Using ngx-translate

ngx-translate is a popular third-party library for implementing i18n in Angular applications, offering simple configuration and usage.

Install Dependencies

Install the ngx-translate core module and HTTP loader:

npm install @ngx-translate/core @ngx-translate/http-loader

Configure AppModule

Import TranslateModule and configure the HTTP loader in app.module.ts:

import { TranslateModule, TranslateLoader } from '@ngx-translate/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http);
}

@NgModule({
  imports: [
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      }
    })
  ]
})
export class AppModule { }

Use TranslateService

Inject TranslateService and set the default language:

import { TranslateService } from '@ngx-translate/core';

constructor(private translate: TranslateService) {
  this.translate.setDefaultLang('en');
}

Load Translation Files

Create translation files (e.g., assets/i18n/en.json and assets/i18n/fr.json) and load them at application startup.

Use Translations in Templates

Use the translate pipe:

<h1>{{ 'HELLO_WORLD' | translate }}</h1>

Angular Internationalization Deep Dive: Date, Number, and Currency Formatting

Beyond multilingual text, Angular provides i18n support for formatting dates, numbers, and currencies using built-in pipes like DatePipe, DecimalPipe, CurrencyPipe, and PercentPipe.

Date Formatting

The DatePipe automatically adjusts date formats based on the locale:

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <p>{{ today | date:'full' }}</p>
    <p>{{ today | date:'mediumTime' }}</p>
  `
})
export class AppComponent {
  today = new Date();
}

Running this code in different locales will adapt the date and time format to local conventions.

Number and Currency Formatting

The DecimalPipe and CurrencyPipe adjust formats based on the locale:

@Component({
  selector: 'app-root',
  template: `
    <p>{{ price | currency:'EUR':'symbol-narrow' }}</p>
    <p>{{ value | percent }}</p>
  `
})
export class AppComponent {
  price = 1234.56;
  value = 0.5;
}

The CurrencyPipe displays currency symbols, and the PercentPipe shows percentages, both adapting to the current locale.

Dynamically Switching Locales

To dynamically switch locales based on user preferences, modify LOCALE_ID or use TranslateService for managing locales.

Using TranslateService:

import { TranslateService } from '@ngx-translate/core';

constructor(private translate: TranslateService) {
  // Set default language
  this.translate.setDefaultLang('en');

  // Get user's preferred language from local storage or browser
  const browserLang = this.getBrowserLang();
  this.translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
}

getBrowserLang(): string {
  return navigator.language || navigator.userLanguage;
}

Provide a language selection option in the UI, calling this.translate.use(lang) to switch locales when a new language is selected.

Testing Internationalized Applications

Ensure the application works correctly across different locales by testing with Angular’s testing frameworks like Jasmine and Karma, using TestBed.overrideProvider to simulate different locales.

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';

describe('AppComponent', () => {
  let component: AppComponent;
  let fixture: ComponentFixture<AppComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [AppComponent],
      providers: [{ provide: LOCALE_ID, useValue: 'fr-FR' }]
    }).compileComponents();

    registerLocaleData(localeFr, 'fr-FR');
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(AppComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should display date in French format', () => {
    expect(component.today | date:'full').toContain('décembre');
  });
});

This approach ensures the application functions correctly in different locales, including proper formatting for dates, numbers, and currencies.

Share your love