4

In my project (Angular 5 + Firebase), the user needs to choose the language at the login page. For that, I add select options and pass the selected value as parameter to the first page, as below:

form_login(f:NgForm){
    if (!f.valid)
      return;
    this.afAuth.auth.signInWithEmailAndPassword(f.controls.email.value, f.controls.password.value)
      .then(ok => {     
        this.router.navigate(["/perfil", f.controls.lang.value]); //>> here
      });
  }

Having this parameter in the url, I retrieve this value and use to translate the entire page, using ngx-translate, like this:

perfil.component.ts

import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';

constructor(private translate: TranslateService,  
    private route: ActivatedRoute, 
    private router: Router) { 

      ...
      translate.setDefaultLang('en');

      let lang = this.route.snapshot.paramMap.get('lang');
      this.translate.use(lang);
      console.log(lang);
    }

perfil.component.html

<h5 translate>Companyprofile</h5>

It works perfect. Except because there is also a navbar, and this component doesn't get the language value at all. Although translating the links labels, the value of each routerLink does not catch the value of the parameter, instead, it sets each link as undefined where should be the value of the language.

navbar.component.ts

import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';

constructor(private translate: TranslateService,  
    private route: ActivatedRoute, 
    private router: Router) { 

      ...
      translate.setDefaultLang('en');

      let lang = this.route.snapshot.paramMap.get('lang');
      this.translate.use(lang);
      console.log(lang); // >> in this case, the console displays `null`
    }

Trying to get this value also at navbar.component.ts, I have this error on console:

navbar.component.ts:36 null
zone.js:2935 GET http://localhost:4200/assets/i18n/null.json 404 (Not Found)

core.js:1440 ERROR HttpErrorResponse {headers: HttpHeaders, status: 404, statusText: "Not Found", url: "http://localhost:4200/assets/i18n/null.json", ok: false, …}

navbar.component.html

 <header id="topnav">
   <ul class="navbar-nav" *ngFor="let p of perfil | async">
     <li class="nav-item">
       <a [routerLink]="['/agenda/', lang]" class="nav-link" translate>Agenda</a>
     </li>
     <li class="nav-item">
       <a [routerLink]="['/admin/', lang]" class="nav-link" translate>Admin</a>
     </li>
   ...
   </ul>
 </header>
 <router-outlet></router-outlet> <!-- here I call other components, e.g perfil.component.html

The lang parameter should bring the value 'en', for example. But, instead, it is undefined.

EDIT: All of my components are children of NavbarComponent. The NavbarComponent has no path, so it isn't possible to set parameter on it, as I did into the other paths.

app.routing.module.ts

const AppRoutes: Routes = [

    {path: '', component: AppComponent, children: [
        { path: '', redirectTo: '/login', pathMatch: 'full' },
        {path: '', component: NavbarComponent, children: [
            {path: 'agenda/:lang', component: AgendaComponent},
            {path: 'perfil/:lang', component: PerfilComponent},
            {path: 'servicos/:lang', component: CadastroServicoComponent},
            {path: 'profissionais/:lang', component: ProfissionaisComponent},
            {path: 'admin/:lang', component: AdminComponent},
            {path: 'dashboard/:lang', component: DashboardComponent},
            {path: 'signup/:lang', component: SignUpFormComponent}
        ]}
    ]}
]    

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

What is wrong with the code?

Atoyansk
  • 233
  • 5
  • 20
  • you are using the `lang` variable to create the `routerLink` inside the `navbar.component.html` if I see that correctly. Do you set this variable somewhere inside the `navbar.component.ts` file? Because in your code I can only find the `lang` variable inside the `perfil.component.ts` file. – Benedikt Schmidt May 05 '18 at 19:34
  • Yes, I am using also inside the `navbar.component.ts`. I have edited my questions and added a comment about the `console.log` in that case. Thanks! – Atoyansk May 05 '18 at 19:46

1 Answers1

0

Alright, here's a suggestion based on what I can see from the code.

Your navbar is probably called inside one of your components as a child component, maybe like this inside your perfil.component.html.

<navbar [parameters...]></navbar>

I'm not 100% sure about this, but I guess that the ActivatedRoute injection only works on components that were loaded through a direct router link, and not for child modules (I'm really not sure about this). This would mean that AcitvatedRoute works for the perfil.component but not for the navbar.component (because not called from your Router). However if your navbar is called like shown above, you could send the lang variable from the perfil.component to the navbar as an input parameter.

Here's the perfil.component.html:

<navbar [lang]="lang"></navbar>

Here's the navbar.component.ts:

// the lang string is now an input parameter
@Input() lang: string;

constructor(private translate: TranslateService, private router: Router) { 
  ...
  // the default language should already been set in the parent component
  // NO NEED: translate.setDefaultLang('en');
}

// the input parameter are not yet availbable in the constructor, but on init
ngOnInit(){
  this.translate.use(lang);
  console.log(lang);
}

Hope this helps.


EDIT

After seeing your command, I see what I got wrong with the navbar. Your navbar component is the first one to be called, the parent component so to speak, building up before the children are initialized.

The problem is that the navbar has its own current route. What you're trying to do is accessing the child route in a parent component. I've found a similar question here with an accepted answer that does not seem to work anymore. You can check it out if necessary, there's another solution, although it looks tricky. So I'm not sure if this is the way to go.

Improvement suggestion

The problem I see is the approach itself with the language as a routing parameter on the child components and not on the parent. The language is currently a route parameter that is set on every single child route of the navbar, resulting in a lot of repetition in your code. All children definitely have to go through the navbar init process anyways, so why not handle the language stuff there? This way you only have to do the language loading at one place in your code.

You probably have to adjust your router in that case, something like this.

    { path: '', redirectTo: '/login', pathMatch: 'full' },
    { path: ':lang', component: NavbarComponent, children: [
        {path: 'agenda', component: AgendaComponent},
        {path: 'perfil', component: PerfilComponent},
        {path: 'servicos', component: CadastroServicoComponent},
        {path: 'profissionais', component: ProfissionaisComponent},
        {path: 'admin', component: AdminComponent},
        {path: 'dashboard', component: DashboardComponent},
        {path: 'signup', component: SignUpFormComponent}
    ]}

Also the code that is currently not working is just not needed anymore, because you don't have to give the language to the child routes anymore.

<header id="topnav">
   <ul class="navbar-nav" *ngFor="let p of perfil | async">
     <li class="nav-item">
       <!-- without / the router routes to children relative to the current path -->
       <!-- no need for the language anymore -->
       <a [routerLink]="['agenda']" class="nav-link" translate>Agenda</a>
     </li>
     <li class="nav-item">
       <a [routerLink]="['admin']" class="nav-link" translate>Admin</a>
     </li>
   ...
   </ul>
 </header>

From your login page, you route directly to the /lang page (which is the navbar) and then initialize the language there (and only do it once). All child components are now children of the lang route instead of having their own lang parameter. In the navbar.component.ts you can probably use the exact same code you are currently using. And in the child components, don't initialize ngx-translate anymore, it's centralized in the parent component now. If you want to use the language parameter in one of the child components, you can still access the current route there, because the parameter is still part of the route, it's just sitting a bit more to the left.

Hope this is accurate enough.

Benedikt Schmidt
  • 2,178
  • 16
  • 20
  • Thanks for answering. In fact, it is the opposite. I've re-edited the `navbar.component.html` code so you can see that the other components are loaded below the navbar. If you see in the route code, in `app.routing.module.ts`, you will notice that `PerfilComponent` is the child of `NavbarComponent`, and the latter has no path, so it is not possible to include parameter in route configuration. – Atoyansk May 05 '18 at 20:36
  • I've updated the answer with a new solution. I personally think the whole problem occurs from a design problem that should be adjustable pretty quickly. Just switch the `lang` parameter from all children to just the parent component and you should be good to go. – Benedikt Schmidt May 05 '18 at 21:23
  • After your update, I tried to follow your suggestions. But, unfortunately, they didn't work out yet. Now, instead of starting on the `login` page, the first page is `navbar` (without any content below), but the URL is `http://localhost:4200/login`. Besides, now the links were built using login as their parent route, e.g `http://localhost:4200/login/agenda`. – Atoyansk May 06 '18 at 12:59
  • if I understood correctly, you have a login page that is prior to your normal pages, therefore you redirect initial calls with empty paths to your login page. So the first page should be fine. From there on, *after* the login itself (where you choose the language), you have to redirect internally (e. g. a router call in your login action) to the navbar link, this of course only works if you have the language parameter from the login page. There's no login route in your router, do you already have a finished login where you select the language? – Benedikt Schmidt May 07 '18 at 00:01
  • Yes, there are both login route (`{ path: '', redirectTo: '/login', pathMatch: 'full' }`, making the login the page to be opened whenever there is navigation for "/") and login page (please see the first snippet of code I posted, where I authenticate the user and retrieve their choice of language). – Atoyansk May 07 '18 at 09:58
  • I guess there should be a route for the login page, something like `{path: 'login', component: LoginComponent}`, otherwise the router will think that "login" is the language parameter and will route it to the ":lang" route. You have to make sure, that the "login" route is defined *before* the ":lang" route, it will take the first one that fits. – Benedikt Schmidt May 07 '18 at 10:17