1

I am doing a i18n service system for my Angular 2 application. To avoid impure pipes, I am adding to the translation pipe an additional parameter, which change will trigger the pipe value to update, so it will update the translated text.

What would be the best practice: Raise an event from the service and subscribe in each component to detect the language change

vs

Set the service variable lang as public and add to it to the pipe as parameter?

I have tested and both of them works.

Using Event emitter:

component.html

<button md-button class="btn-register">{{'Register' | _:lang}}</button>

component.ts

export class LoginComponent implements OnInit {
    loginForm: FormGroup;
    public lang: string;
    onLangChange: EventEmitter<LangChangeEvent>;

    constructor(private _fb: FormBuilder, private _authService: AuthService, private tr:TranslateService) {

    }

    ngOnInit():any {

        //monitor language change
        this.onLangChange = this.tr.onLangChange.subscribe( (event: LangChangeEvent) => {
            this.lang = event.lang;
        });

        this.loginForm = this._fb.group({
            email: ['', Validators.required],
            password: ['', Validators.compose( [Validators.required, hasNumbers] ) ]
        });

    }

    onSubmit(form) {
        this._authService.login( this.loginForm.value );
    }
}

translate.service.ts

import {Injectable, EventEmitter} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import {Languages} from "../config/Constants";

export interface LangChangeEvent {
    lang: string;
}

@Injectable()
export class TranslateService {

    public lang: string;
    dictionary: any = {};

    /**
     * An EventEmitter to listen to lang change events
     * onLangChange.subscribe((params: LangChangeEvent) => {
     *     // do something
     * });
     * @type {ng.EventEmitter<LangChangeEvent>}
     */
    public onLangChange: EventEmitter<LangChangeEvent> = new EventEmitter<LangChangeEvent>();

    constructor(private http: Http, private path:string) {

        let lang = this.detectLang();
        this.setLanguage(lang);
    }

    /**
     * A standard alias used for the translate function
     * @param val
     * @returns {string}
     * @private
     */
    public _(val: string) {
        return this.dictionary[val] ? this.dictionary[val] : val;
    }

    public setLanguage(lang: string) {

        if(Languages[lang]) {
            this.loadLanguage(lang, Languages[lang].path);
        }
    }

    private loadLanguage(lang:string, url: string) {
        return this.http.get(url).toPromise()
            .then( dictionary =>
            {
                this.dictionary = dictionary.json();
                this.lang = lang;
                this.onLangChange.emit({ lang: this.lang });
            })
            .catch( error => console.log(error) );
    }

    private detectLang(): string {
        if (typeof window === 'undefined' || typeof window.navigator === 'undefined') {
            return 'en';
        }
        let browserLang: any;
        if (typeof window.navigator['languages'] !== 'undefined' && window.navigator['languages'].length > 0) {
            browserLang = window.navigator['languages'][0].indexOf('-') !== -1 || window.navigator['languages'].length < 2 ? window.navigator['languages'][0] : window.navigator['languages'][1];
        } else {
            browserLang = window.navigator['language'] || window.navigator['browserLanguage'];
        }

        return browserLang && browserLang.length ? browserLang.split('-')[0] : 'en'; // use navigator lang if available
    }
}

Using a public variable:

<button md-button class="btn-register">{{'Register' | _:tr.lang}}</button>

component.ts

export class LoginComponent implements OnInit {
    loginForm: FormGroup;

    constructor(private _fb: FormBuilder, private _authService: AuthService, private tr:TranslateService) {

    }

    ngOnInit():any {

        this.loginForm = this._fb.group({
            email: ['', Validators.required],
            password: ['', Validators.compose( [Validators.required, hasNumbers] ) ]
        });

    }

    onSubmit(form) {
        this._authService.login( this.loginForm.value );
    }
}

translate.service.ts

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import 'rxjs/add/operator/toPromise';
import {Languages} from "../config/Constants";

export interface LangChangeEvent {
    lang: string;
}

@Injectable()
export class TranslateService {

    public lang: string;
    dictionary: any = {};


    constructor(private http: Http, private path:string) {

        let lang = this.detectLang();
        this.setLanguage(lang);
    }

    /**
     * A standard alias used for the translate function
     * @param val
     * @returns {string}
     * @private
     */
    public _(val: string) {
        return this.dictionary[val] ? this.dictionary[val] : val;
    }

    public setLanguage(lang: string) {

        if(Languages[lang]) {
            this.loadLanguage(lang, Languages[lang].path);
        }
    }

    private loadLanguage(lang:string, url: string) {
        return this.http.get(url).toPromise()
            .then( dictionary =>
            {
                this.dictionary = dictionary.json();
                this.lang = lang;
            })
            .catch( error => console.log(error) );
    }

    private detectLang(): string {
        if (typeof window === 'undefined' || typeof window.navigator === 'undefined') {
            return 'en';
        }
        let browserLang: any;
        if (typeof window.navigator['languages'] !== 'undefined' && window.navigator['languages'].length > 0) {
            browserLang = window.navigator['languages'][0].indexOf('-') !== -1 || window.navigator['languages'].length < 2 ? window.navigator['languages'][0] : window.navigator['languages'][1];
        } else {
            browserLang = window.navigator['language'] || window.navigator['browserLanguage'];
        }

        return browserLang && browserLang.length ? browserLang.split('-')[0] : 'en'; // use navigator lang if available
    }
}
albanx
  • 5,396
  • 8
  • 57
  • 89
  • Just a hint. You shouldn't use `EventEmitter` for everything else than `@Output()` in a component. In services use `Observable` or `Subject` instead. See also http://stackoverflow.com/questions/34376854/delegation-eventemitter-or-observable-in-angular2 – Günter Zöchbauer Aug 19 '16 at 05:11
  • I removed the event emitter. I am keeping it with the public variable for the moment, as less code is needed – albanx Aug 19 '16 at 09:03
  • If you replace it by `Subject` there shouldn't be any difference except `next()` instead of `emit()` – Günter Zöchbauer Aug 19 '16 at 09:05
  • @GünterZöchbauer but that will keep me adding on every component the subscribe, instead by just using ``tr.lang`` exposing it as public variable I just need to inject the service – albanx Aug 19 '16 at 09:08
  • Sorry, I don't understand your last comment. Why would you not need to subscribe when you use `EventEmitter`. `EventEmitter` currently just extends `Observable` or `Subject` but that's not guaranteed to stay this way. – Günter Zöchbauer Aug 19 '16 at 09:15
  • I will add the code with my current solution – albanx Aug 19 '16 at 09:23
  • @GünterZöchbauer when the ``lang`` variable of the TranslateService changes, it will refresh the pipe value, rather than using an Observable, I just declare it as public and attached to the pipe ``{{'Register' | _:tr.lang}}``. It this good practice in your opinion, or is better to go through an Observable/Subject? – albanx Aug 19 '16 at 09:29
  • If you change `EventEmitter` in the service by `Subject` it will behave the same. This is not related to your original question. This is only about that `EventEmitter` should not be used for anything but `@Output()`s. I think it doesn't make much difference if you use `lang` or `tr.lang` except that the view (template) is coupled to the service if you use `tr.lang` and otherwise only the component is coupled to the service but the view isn't. – Günter Zöchbauer Aug 19 '16 at 09:46
  • For my library [angular2localization](https://github.com/robisim74/angular2localization) I used the pure pipes, with a public variable as a parameter to simplify the use in components through a `getter` of the language. The services still emit events because they are useful in several cases. – robisim74 Aug 24 '16 at 10:31

0 Answers0