Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically load different css files in angular2 application based on user's language

I have an Angular2 application and it need to support multiple languages. Some of these languages may be RTL (i.e Persian) and some will be LTR (i.e. English). Apart from localizing strings, I need to style my components based on the direction of the language.

Now using this helper, I can have two separate files (i.e. app-rtl.scss & app-ltr.scss) which both import a single scss file (i.e. app.scss) which allows me to write my scss code in one place and automatically output two files; one for each direction.

The problem is now I need to somehow reference the suitable css file based on the user's language. So if the user's language is English then I should have <link href="app-ltr.css" rel="stylesheet"> in the header and if it's RTL, <link href="app-rtl.css" rel="stylesheet">. Furthermore I want to add bootstrap to my project and that also has two different output files for LTR and RTL.

Is there any clean way to achieve this functionality?

What I've tried:

I've create two dummy component (one for LTR and one for RTL) without any template and then assign the respective scss file with styleUrls and set encapsulation: ViewEncapsulation.None so that it becomes a global style. Then I initialize both of them in my root component template with an *ngIf and check if the language is LTR or not.

This would work on the first page load because only one component (let's say component LTR) is active, but as soon as you change the language (i.e the second component (RTL) becomes active and thus LTR is removed) then the styles associated with the LTR stays on the page and is not removed. So then you have both RTL and LTR styles on your page which is not intended.

like image 780
Sobhan Avatar asked Jan 11 '17 23:01

Sobhan


2 Answers

You'd need to conditionally add or remove the style from the browser:

to add

loadIfNot(url,cssId){

     if (!document.getElementById(cssId)){
        var head  = document.getElementsByTagName('head')[0];
        var link  = document.createElement('link');
        link.id   = cssId;
        link.rel  = 'stylesheet';
        link.type = 'text/css';
        link.href = url;
        link.media = 'all';
        head.appendChild(link);
    }else{
        document.getElementById(cssId).disabled = false;///i fit's already there, enable it
    }

  }

and for remove

 disable(cssId){
     document.getElementById(cssId).disabled = true
 }

Disable and Enable is because browser tends to cache your styles, and in order to make them enable or disable, you'd change that attribute

like image 83
Milad Avatar answered Sep 19 '22 23:09

Milad


1) I would suggest you to use lazy loaded modules instead of dynamic css files (please take a look at official Angular docs for more info)

2) I would also suggest to put current language to some service, inject service into the appropriate component and to use needed css class. Advantages:

2.1) component style does not depend on body element - just on value in service - this is more good/correct for unit testing

2.2) there could be usecases when it's not enough to use LTR vs RTL classes, but to use some additional classes for particular languages (for instance, table columns could have different width for English, German, etc languages)

3) loading css dynamically could cause UI issues: initial styles are applied first and then they 'jump' to "other" styles (when the appropriate css file is loaded). So 1) & 2) are much better (IMHO indeed)

UPDATE 2017-06-07 (just for being more clear)

app-routing.module could be like:

import { NgModule }             from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

export const routes: Routes = [
    { path: '', redirectTo: '/home', pathMatch: 'full'},
    { 
        path: 'home', 
        loadChildren: 'app/my-home-page/my-home-page.module#MyHomePageModule' 
    },
    ...
];

@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule {}

my-global.service could be like:

@Injectable()
export class MyGlobalService {
    currentLanguageDirection: string; // i.e. 'ltr' or 'rtl'
    currentLanguage: string;          // i.e. 'en', 'de', 'ru', etc
}

my-home-page.component.ts could be:

export class MyHomePageComponent {
    ...

    constructor(
        private myGlobalService: MyGlobalService, // inject global service
        ...
    ) {
    }

    ...
}

my-home-page.component.html could be:

<div [ngSwitch]="myGlobalService.currentLanguageDirection">
    <my-home-page-rtl *ngSwitchCase="'rtl'"></my-home-page-rtl>
    <my-home-page-ltr *ngSwitchDefault></my-home-page-ltr>
</div>

I.e. two additional components could be used: my-home-page-ltr and my-home-page-rtl. They will have separate scss|css files.

If you need unique implementation for German then my-home-page-ltr.component.html could be:

<div [ngSwitch]="myGlobalService.currentLanguage">
    <my-home-page-ltr-de *ngSwitchCase="'de'"></my-home-page-ltr-de>
    <div *ngSwitchDefault>
        ... (default html code)
    </div>
</div>

I.e. a separate my-home-page-ltr-de component will have separate scss|css for German.

P.S. Please note that you could reuse html files by setting them in @Component:

@Component({
    selector: 'my-home-page...',
    templateUrl: './my-home-page....html', // <-------------- reuse here
    styleUrls: ['./my-home-page....scss']
})

P.P.S. I would recommend not to use manually creation/deletion of components. Usage of ngSwitch or ngIf is a more correct way (IMHO)

P.P.P.S. Advantages are the following. Component and its children should be loaded/released by angular automatically. Language switching will be performed very fast because current component is loaded with its children - you do not need to load styles of all components during language switching.

like image 26
marbug Avatar answered Sep 19 '22 23:09

marbug