Angular 5 - ngx-translate within routerLink - javascript

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?

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.

Related

Angular nested routing is removed by the main router outlet

I have a app in which I have a header and a sidebar. It looks like this:
#app.component.html
<mat-drawer-container class="sidenav-container">
<app-side-nav></app-side-nav>
<mat-drawer-content>
<app-header></app-header>
<main>
<router-outlet></router-outlet>
</main>
</mat-drawer-content>
</mat-drawer-container>
And routing configuration is:
const routes: Routes = [
{ path: 'identity', loadChildren: './identity-registry/identity-registry.module#IdentityRegistryModule' }
];
Now upon clicking on identity the identity module will load a nested nav menu within it. The identity module has 3 component (IdentityRegistryComponent,MyIdentityComponent, UsersComponent) and it has its own routing configuration.
const routes: Routes = [
{
path: '',
component: IdentityRegistryComponent
},
{
path: 'my-identity',
component: MyIdentityComponent
},
{
path: 'users',
component: UsersComponent
}
];
and the nested route looks like this:
###IdentityRegistryComponent
<nav mat-tab-nav-bar>
<a mat-tab-link [routerLink]="['./my-identity']">My Identity</a>
<a mat-tab-link [routerLink]="['./users']">Users</a>
</nav>
<router-outlet></router-outlet>
But unfortunately, whenever I click on identity, its load the IdentityRegistryComponent properly. But click on my-identity, disappear the nested routes and load the respective component only. But it should not be like that. The nested loop should be there and upon clicking on my-identity, it should load the respective component on router-outlet. I do not know how to make it working?
Besides, is there anyway that, if i click on identity from the nav, it will load the IdentityRegistryComponent and by default MyIdentityComponent will be loaded in a nested routes zone?
for better understanding, i have add the git link:
testApp
If you want to use the router outlet for this module, you need to specify the component for this 'child'.
const routes: Routes = [{
path: 'identity',
component: AppComponent,
loadChildren: './identity-registry/identity-registry.module#IdentityRegistryModule'
}];
If you want all your routes to use the same component you can also define it like so:
const routes: Routes = [{
path: '',
component: AppComponent,
children: [{
path: 'identity',
component: NestedNavComponent, // Add a component here for nested router-outlets
loadChildren: './identity-registry/identity-registry.module#IdentityRegistryModule'
},{
path: 'another-route',
loadChildren: './another-route/another-route.module#AnotherRouteModule'
}]
}];
Update (See the nested component reference I added):
Routes work hierarchical and that includes router-outlets in nested routes. The application is built in layers for child routes. If you consider that, you can define your child routes the same way as you designed your ui logic.

Ionic 4 and using material tabs router outlet

I am wanting to use Material Tab's (https://material.angular.io/components/tabs/api#MatTabLink) within my Ionic 4 project, now, the requirements are that I need to house multiple views in a tab and the first thought was that I can use a new ion-router-outlet or router-outlet within my parent component.
Bare in mind that I do already have one router outlet for the main app.
I am lazy loading the main chat routes in my app-routing.module.ts, this page is responsible for loading the tabs.
{ path: 'chat', loadChildren: './chat/chat.module#ChatPageModule', canActivate: [ AuthGuard ]}
Now, in my chat.module.ts I have the following routes:
{ path: '', component: ChatPage },
{ path: 'active', component: ActivePage },
{ path: 'messages', component: MessagesPage },
{ path: 'teams', component: TeamsPage }
ChatPage component is my parent tab view page. The others I am wanting to be in a tab.
The HTML for displaying these tabs is in chat.page.html and looks like this:
<nav mat-tab-nav-bar>
<a mat-tab-link
*ngFor="let link of routeLinks"
[routerLink]="link.path"
routerLinkActive #rla="routerLinkActive"
[active]="rla.isActive">
{{ link.label }}
</a>
</nav>
<router-outlet></router-outlet>
I have also tried <ion-router-outlet></ion-router-outlet> but this throws up more issues.
The main issue here is that the routes look as though they are loading up in the main router outlet rather than the child one, I have tried adding the name attribute to the mark up but my IDE states that it's not valid and doesn't seem to work.
Ok, I have figured it out, and I am going to look stupid for not trying this before but the issue was that in order to use this child router-outlet the routes I wanted in tabs need to child routes.
{ path: '', component: ChatPage, children: [
{ path: 'active', component: ActivePage },
{ path: 'messages', component: MessagesPage },
{ path: 'teams', component: TeamsPage }
] },

Angular- How to route parent/child with repeated component?

I'm trying to figure out the cleanest way to set up my Angular routing.
I have a parent route, and inside as a child, I'd like to repeat the same component multiple times (a list of all the locations). Each of the location (child) component occurrences will look the same, just with different data that I will need to pass in. (I'll do this with *ngFor).
Here's what I'm thinking so far:
<router-outlet>//for the parent
<router-outlet name="aux">
<ul *ngFor="let location in service.locations"
</router-outlet>
//where I list out all the locations^
</router-outlet>
on my app-routing.module.ts file:
const routes: Routes = [
{ path: 'main', component: MainComponent }, //lists all locations
{ path: 'locations/:id', component: LocationsComponent }, //view 1
location
{ path: '', pathMatch: 'full', redirectTo: '/main},
];
(I want to have both parent and child appear on initial load. Should I modify that?^)
Now this is the part I don't understand as much, I need to set my route configs. Is this close? And does it goin my app-routing.module.ts file as well?
#RouteConfig([
{path:'/', name: 'MainPath', component: MainComponent,
useAsDefault: true},
{aux:'/auxRoute', name: 'AuxPath', component: SecondComponent}
])
Thanks! Please let me know if you need clarification.

Angular 6 - navigation to child route refreshes whole page

So I'm using Angular 6 and I'm trying to navigate to a child route from the parent route. The navigation is successful, however there is an unwanted page refresh upon rendering the child component. In other words, the navigation works but it also refreshes the page for no apparent reason. Here is my code:
const appRoutes: Routes = [
{
path: "parent/:param1/:param2", component: ParentComponent,
children: [
{ path: ":param3", component: ChildComponent }
]
},
{ path: "", redirectTo: "/index", pathMatch: "full" },
{ path: "**", redirectTo: "/index" }
];
My parent component looks like this:
import { Component } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "my-parent",
templateUrl: "./parent.component.html"
})
export class ParentComponent {
param1: string;
param2: string;
loading: boolean;
tutorials: any[];
constructor(public route: ActivatedRoute) {
this.loading = true;
this.param1= this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
// get data here
}
}
And my child component looks like this:
import { Component } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "my-child",
templateUrl: "./child.component.html"
})
export class ChildComponent {
param1: string;
param2: string;
param3: string;
loading: boolean;
result: any;
constructor(public route: ActivatedRoute) {
this.loading = true;
this.param1= this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
this.param3 = this.route.snapshot.params.param3;
}
}
Now, the way I try to navigate from the parent component to the child component is the following one:
<a [routerLink]="['/parent', param1, param2, param3]">
<b>Navigate</b>
</a>
As I've said, the navigation is successful, but there is an unwanted page refresh which I want to get rid of and I haven't been able to find a working solution. I don't really know what's causing it. I am new to Angular 6.
Thanks in advance for your answers.
EDIT: added parent component html
<router-outlet></router-outlet>
<div class="row" *ngIf="route.children.length === 0">
// content here
</div>
So I found a working solution, which while not very elegant, it... works. In my parent component I created a method like this one:
constructor(public route: ActivatedRoute, private router: Router) {
this.loading = true;
this.param1 = this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
// get data
}
navigateToChild(param3: string) {
this.router.navigate([param3], { relativeTo: this.route });
}
And in the parent template, I did this:
<a (click)="navigateToChild(paramFromServer)">
<b>Navigate</b>
</a>
No more refreshes for this one.
Thank you for your help everyone.
Remove the leading / from [routerLink]= "['/parent'...]" url. The / is telling the app to find the component route from the root of the application whereas no leading / will try to redirect to the child relative to the current component.
Also make sure you have added a <router-outlet> to the parent.component.html as that is where the child component will first try to be added on navigate. If that is not available it might be causing a full refresh to load in the new component from scratch.
const appRoutes: Routes = [
{
path: "parent/:param1/:param2", component: ParentComponent,
children: [
{ path: ":param3", component: ChildComponent }
]
},
// remove this 2 lines
// redirect to index thing is not needed
];
You didn't define param3 in your ParentComponent. Also you may need to change the strategy of params so your ChildComponent can retrieve the params from its parent.
Please check this stackblitz:
https://stackblitz.com/edit/angular-tvhgqu
In my case 'href' was the problem. Using routerLink solved the problem.
Problematic Approach:
<a href='/dashboard/user-details'>User</a>
Solution:
<a routerLink='/dashboard/user-details'>User</a>

How to subscribe to param changes in child route?

I have these routes
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{
path: 'explore',
component: ExploreComponent,
children: [
{ path: '', component: ProductListComponent },
{ path: ':categorySlug', component: ProductListComponent }
]
}
];
This means that the user can go to
/explore (no category)
or
/explore/computers (category computers)
From the parent (ExploreComponent), I want to be able to subscribe to the categorySlug param change, and handle the event of course. How can I do this?
EDIT:
I tried subscribing using:
this.activatedRoute.firstChild.params.subscribe(console.log);
And it gives me exactly what I want, but it dies once I go to /explore (without category). It only works when navigating using /explore/:categorySlug links.
You can subscribe to the params in your component ang get the parameter, e.g. like this:
import { Router, ActivatedRoute } from '#angular/router';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.params.subscribe(params => {
this.categorySlug= params['categorySlug '];
});
// do something with this.categorySlug
}
Side note: In general you use a kind of master detail structure in your web app, so the first path goes to the master and the second one goes to the detail, each served with a different component, but in case that you want to use the same component for both of them, or there is no such a master-detail relationship, you should check if the parameter is null.

Categories