Core: performance question: directive vs pipe

Created on 13 Jul 2017  路  11Comments  路  Source: ngx-translate/core

I'm submitting a ... (check one with "x")

[ ] bug report => check the FAQ and search github for a similar issue or PR before submitting
[x] support request => check the FAQ and search github for a similar issue before submitting
[ ] feature request

Is there a performance advantage to using the translate directive vs the (impure) translate pipe?

Most helpful comment

@piotr-szybicki
The pipe

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@services/translate.service';

@Pipe({
  name: '_'
})
export class TranslatePipe implements PipeTransform {
  constructor(public trans: TranslateService) {}

  transform(value: string, ...args): string {
    if (args.length > 1) {
      args.shift();
    }
    return this.trans._(value, args);
  }
}

The service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LANGUAGES } from '@constants/languages';
import { Store } from '@ngrx/store';
import AppState from '@state/app.state';
import { environment } from 'environments/environment';

@Injectable({
  providedIn: 'root'
})
export class TranslateService {
  public lang: string;
  dictionary: any = {};
  _collector: any = {};
  deployUrl = environment.deployUrl;

  constructor(private http: HttpClient, private store: Store<AppState>) {
    this.store.select(state => state.settings).subscribe(settings => this.setLanguage(settings.language));
  }

  public _(val: string, args = []) {
    const translated = this.dictionary[val] ? this.dictionary[val] : val;
    this._collector[val] = translated;

    if (args.length > 0) {
      return this.sprintf(translated, args);
    }

    return translated;
  }

  public setLanguage(lang: string) {
    if (LANGUAGES[lang]) {
      this.loadLanguage(lang, `${this.deployUrl}/${LANGUAGES[lang].path}`);
    }
  }

  private sprintf(val: string, args: Array<string>) {
    return args.reduce((p, c) => p.replace(/%s/, c), val);
  }

  private loadLanguage(lang: string, url: string) {
    console.log(JSON.stringify(this._collector));

    return this.http.get(url).subscribe(dictionary => {
      this.dictionary = dictionary;
      this.lang = lang;
    });
  }
}

A component usage (that also switch the language):

import { Component, OnInit } from '@angular/core';
import { environment } from 'environments/environment';
import { Store } from '@ngrx/store';
import AppState from '@state/app.state';
import { buildSetLanguage } from '@state/actions/settings.actions';
import { TranslateService } from '@services/translate.service';
@Component({
  selector: 'app-footer',
  templateUrl: './footer.component.html',
  styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
  brand = environment.brand;
  version = environment.appVersion;
  constructor(private store: Store<AppState>, public trans: TranslateService) {}

  ngOnInit() {}

  setLanguage(lang) {
    this.store.dispatch(buildSetLanguage(lang));
  }
}

.html

<footer class="footer">
  <p class="footer__copyright">
    <a class="footer__link" href="https://{{ brand.url }}">漏 {{ brand.name }}</a> |
    <a class="footer__link" (click)="setLanguage('en')"> English </a> |
    <a class="footer__link" (click)="setLanguage('sq')"> Shqip </a>
  </p>
  <p class="termsofuse">
    <a class="footer__link" routerLink="/legal/about">{{ 'About' | _: trans.lang }} </a> |
    <a class="footer__link" routerLink="/legal/terms">{{ 'Terms of Use' | _: trans.lang }}</a> |
    <a class="footer__link" routerLink="/legal/privacy">{{ 'Privacy and GDPR' | _: trans.lang }}</a>
  </p>
  <p class="termsofuse">Version {{ version }}</p>
</footer>

Hope this helps.

The basic idea is that the language get changed/checked only when it changes, not on every change detection cycles.

All 11 comments

I think that the pipe has better performance advantage, but I don't have any benchmark to support that

In angular-translate, the official documentation said using directive is better than pipe. I am not sure whether this is true in ngx-translate.

https://angular-translate.github.io/docs/#/guide/05_using-translate-directive

This "official" documentation is for "angular-translate", the AngularJS 1.x translate library.

so, for better performance, what is better to use? pipe or the directive?

One thing I know, is that translate.pipe is an impure pipe.

Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

And it really does call it 'all the time'. The comment for that is:

// required to update the value when the promise is resolved

Got it, but seems really inefficient.

I'm not sure how is it with directive, will check that.

@tasiek, any update?

@ocombe, do you know if there is a difference between using the translation in html vs using it in the component?

Thanks!

Is there any update?

@bgBond When use pipe transform show first empty string And when use directive, show first path to translate string. But I don't know which performance is better?!

I ask this question [here] (https://stackoverflow.com/questions/53156920/which-performance-are-better-ngx-translate-directive-or-pipe)

Using impure pipes can lead to several performance issues, I tested it and leads to huge number calls in the component, and template rendering, specially if we have a lot of strings or change cycles in your app.

I did a similar solution with a pure pipe, with one more input in order to make it change in the language change:
{{'Hello' | _:trans.lang}}

where _ is the pipe to call and trans.lang is a public variable of the translate service. In this case the translation will trigger only if the trans.lang changes to another language.

@albanx can you share the code how did you manage to make the pipe returning the string.

@Pipe({
  name: 'ttttranslate'
})
export class TttTranslatePipe implements PipeTransform {

  constructor(private translate: TranslateService) {}

  transform(value: string, language: string): Observable<String> {
    return this.translate.get(value);
  }
}

I did something like this but the problem is that call to translate returns Observable. That requires me to chain the | async pipe at the end of every invocation. Also, your approach requires to inject translate service into every component. Do you have any thoughts on that?

@piotr-szybicki
The pipe

import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@services/translate.service';

@Pipe({
  name: '_'
})
export class TranslatePipe implements PipeTransform {
  constructor(public trans: TranslateService) {}

  transform(value: string, ...args): string {
    if (args.length > 1) {
      args.shift();
    }
    return this.trans._(value, args);
  }
}

The service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { LANGUAGES } from '@constants/languages';
import { Store } from '@ngrx/store';
import AppState from '@state/app.state';
import { environment } from 'environments/environment';

@Injectable({
  providedIn: 'root'
})
export class TranslateService {
  public lang: string;
  dictionary: any = {};
  _collector: any = {};
  deployUrl = environment.deployUrl;

  constructor(private http: HttpClient, private store: Store<AppState>) {
    this.store.select(state => state.settings).subscribe(settings => this.setLanguage(settings.language));
  }

  public _(val: string, args = []) {
    const translated = this.dictionary[val] ? this.dictionary[val] : val;
    this._collector[val] = translated;

    if (args.length > 0) {
      return this.sprintf(translated, args);
    }

    return translated;
  }

  public setLanguage(lang: string) {
    if (LANGUAGES[lang]) {
      this.loadLanguage(lang, `${this.deployUrl}/${LANGUAGES[lang].path}`);
    }
  }

  private sprintf(val: string, args: Array<string>) {
    return args.reduce((p, c) => p.replace(/%s/, c), val);
  }

  private loadLanguage(lang: string, url: string) {
    console.log(JSON.stringify(this._collector));

    return this.http.get(url).subscribe(dictionary => {
      this.dictionary = dictionary;
      this.lang = lang;
    });
  }
}

A component usage (that also switch the language):

import { Component, OnInit } from '@angular/core';
import { environment } from 'environments/environment';
import { Store } from '@ngrx/store';
import AppState from '@state/app.state';
import { buildSetLanguage } from '@state/actions/settings.actions';
import { TranslateService } from '@services/translate.service';
@Component({
  selector: 'app-footer',
  templateUrl: './footer.component.html',
  styleUrls: ['./footer.component.scss']
})
export class FooterComponent implements OnInit {
  brand = environment.brand;
  version = environment.appVersion;
  constructor(private store: Store<AppState>, public trans: TranslateService) {}

  ngOnInit() {}

  setLanguage(lang) {
    this.store.dispatch(buildSetLanguage(lang));
  }
}

.html

<footer class="footer">
  <p class="footer__copyright">
    <a class="footer__link" href="https://{{ brand.url }}">漏 {{ brand.name }}</a> |
    <a class="footer__link" (click)="setLanguage('en')"> English </a> |
    <a class="footer__link" (click)="setLanguage('sq')"> Shqip </a>
  </p>
  <p class="termsofuse">
    <a class="footer__link" routerLink="/legal/about">{{ 'About' | _: trans.lang }} </a> |
    <a class="footer__link" routerLink="/legal/terms">{{ 'Terms of Use' | _: trans.lang }}</a> |
    <a class="footer__link" routerLink="/legal/privacy">{{ 'Privacy and GDPR' | _: trans.lang }}</a>
  </p>
  <p class="termsofuse">Version {{ version }}</p>
</footer>

Hope this helps.

The basic idea is that the language get changed/checked only when it changes, not on every change detection cycles.

Was this page helpful?
0 / 5 - 0 ratings