scrollIntoView when Angular Router change - javascript

I am trying to use scrollIntoView when my page is loaded and when a user clicks on one of the menu buttons.
Therefore, I am subscribing to the route params in ngOnInit (Also tried in ngAfterViewInit) and inside there I activate "scrollIntoView()".
The issue is that it is not working on the First loading of the page (if the client enter direcly to www.website.co.il/home/componentName instead of www.website.co.il/home) but only if I change my route after the page is loaded.
app-routing.module.ts
{ path: '', component: HomeComponent },
{ path: 'home/:section', component: HomeComponent },
{ path: 'home', component: HomeComponent }
navbar.component.html
<li
class="menuItem"
*ngFor="let menuItem of (menuItems$ | async).menuItems; trackBy: trackByFunction">
<a
class="menuItem__link"
[routerLink]="['home', menuItem.link]">{{ menuItem.name }}</a>
</li>
our-services.component.ts
this.route.params.subscribe(params => {
if(params.section === COMP_LINK_NAME) {
this.container.nativeElement.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" });
}
});
}
COMP_LINK_NAME is const parameter of our-services component

Related

Angular child route keeps redirecting to parent

I'm trying to add child routes to a sidebar menu using Angular 12. When I click on a child route link it briefly goes to the child component then automatically redirects back to the parent component. I can't figure out why.
I declare all of my routes in the parent component's routing module:
const routes: Routes = [
{
path: '',
component: AppManagementComponent,
resolve: {
data: SupportingDataResolverService
},
children: [
{
path: 'users',
loadChildren: () => import('./user-management/user-management.module').then((m) => m.UserManagementModule),
data: {
breadcrumb: 'Account Settings'
}
},
{
path: 'roles',
loadChildren: () => import('./app-roles/app-roles.module').then((m) => m.ApplicationsRolesModule),
data: {
breadcrumb: 'Application Roles'
}
},
{
path: 'data',
loadChildren: () => import('./data-management/data-management.module').then((m) => m.DataManagementModule),
children: [
{
path: '',
pathMatch: 'full',
redirectTo: 'orgcodes',
},
{
path: 'orgcodes',
loadChildren: () =>
import('./data-management/organisation-codes/organisation-codes.module').then((m) => m.OrganisationCodesModule),
data: {
breadcrumb: 'Organisation Codes'
}
},
{
path: 'appdata',
pathMatch: 'full',
loadChildren: () =>
import('./data-management/app-data/app-data.module').then((m) => m.AppDataModule),
data: {
breadcrumb: 'App Data'
}
},
{
path: 'usercodes',
pathMatch: 'full',
loadChildren: () =>
import('./data-management/user-codes/user-codes.module').then((m) => m.UserCodesModule),
data: {
breadcrumb: 'User Codes'
}
}
]
},
I link to the child routes in the parent component sidebar menu:
<mat-sidenav-container>
<mat-sidenav
mode="side"
opened
position="start" >
<mat-list>
<mat-list-item
class="sidebar-item"
routerLink="users"
routerLinkActive="selected"
>Users
</mat-list-item>
<mat-list-item
class="sidebar-item"
routerLink="roles"
routerLinkActive="selected"
>
Roles
</mat-list-item>
<mat-accordion>
<mat-expansion-panel
routerLink="data"
routerLinkActive="selected"
>
<mat-expansion-panel-header>
<mat-panel-title>
Data Management
</mat-panel-title>
</mat-expansion-panel-header>
<mat-list>
<mat-list-item
class="sidebar-item"
routerLink="data/orgcodes"
><mat-icon>arrow_right</mat-icon> Organisation Codes
</mat-list-item>
<mat-list-item
class="sidebar-item"
routerLink="data/appdata"
><mat-icon>arrow_right</mat-icon> App Data
</mat-list-item>
<mat-list-item
class="sidebar-item"
routerLink="data/usercodes"
><mat-icon>arrow_right</mat-icon> User Codes
</mat-list-item>
</mat-list>
</mat-expansion-panel>
</mat-accordion>
</mat-list>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
The router links to 'users' and 'roles' work fine. When I click on the 'data' router link it redirects to 'data/orgcodes' as it should, but when I click on one of the other child route links like 'data/appdata' or 'data/usercodes' it briefly goes to that particular child component then redirects back to 'data/orgcodes'. If I remove the 'orgcodes' redirect, it will redirect back to 'data' instead.
I've tried changing the child routerLink syntax to "./data/usercodes" and the same thing happens. I tried changing it to "/data/usercodes" and got Error: Cannot match any routes. URL Segment: 'data/usercodes'. I also added patchMatch: 'full' to the child routes in the routing module and got the same result.
If I manually type in the child route into the browser it works like this: www.myapp.com/appmanagement/data/usercodes. Any help would be appreciated.
UPDATE:
I added some code to listen to when the route changes. I noticed a 'NavigationCancel - navigation id is not equal to the current navigation id' appears when the page redirects back to the parent component.

How to use Router link active for all children

I have side nav with nested routes.my in click on the first button inside navbar it goto the doctors/doctors-list and router link active work perfectly. but I have other children routers like doctors/creat, doctors/update:id when I am going to those routes my router link is not activated as expected. my app structure is when the user first clicks on the doctor button it doctors/doctors-list
when user select doctor from the list route will be changed to the 'doctors/update:id'.how to slove this issue
<a routerLink="doctors/doctors-list" routerLinkActive="active"></a>
my routes config
{
path: 'doctors',
component: DoctorsHomeComponent,
children: [
{
path: 'doctors-list',
component: DoctorsComponent,
},
{
path: 'doctors/creat',
component: DoctorsPageComponent,
},
{
path: 'update-doctor/:id',
component: DoctorsPageComponent,
}
]
}
Have you try this
#Component
import { Router } from '#angular/router';``
constructor(public router: Router) {}
isdoctorsActive(exact) {
return this.router.isActive('doctors/', exact);
}
#html
<a routerLink="doctors/doctors-list" [ngClass]="{'active': isdoctorsActive(false)}"></a>
Have you added the following tag in your doctor components html page
<router-outlet></router-outlet>
You need to use lazy routing here -
In your app-routing.module.ts you can load doctor module like below code
const routes: Routes = [
{
path: '',
component: LandingComponent,
},
{
path: 'doctors',
loadChildren: 'doctor.module#DoctorModule'
}
];
Once it loads doctor module then it loads the component which it founds in the URL
Below code will be in your doctor-routing.module.ts
const routes: Routes = [
{ path: 'doctors-list', component: DoctorsComponent},
{ path: 'create', component: DoctorsPageComponent},
{ path: 'create/:id', component: DoctorsPageComponent}
];
Above routes you can use like this -
It loads doctors component
<a routerLink="doctors/doctors-list" routerLinkActive="active"></a>
It loads doctors page component in create mode
<a routerLink="doctors/create" routerLinkActive="active"></a>
It loads doctors page component in edit mode
<a routerLink="['doctors/create', id]" routerLinkActive="active"></a>

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 5 - ngx-translate within routerLink

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.

Vue.js: Add router-link-active to vue-router component loaded for root

I'm having some trouble with vue-router. My routes are set up like this:
const routes = [
{ path: '/', component: a },
{ path: '/a', component: a },
{ path: '/b', component: b },
{ path: '/c', component: c }
]
As you can see, I want to load component a when the page loads. But I don't want the URL to change to /a when the page loads. However I would like to trigger the router-link-active-class on the <router-link> related to the a component, even though I'm still at /.
I've tried with some simple JS like:
if(window.location.hash === '#/') {
var el = document.querySelector('#drawerList').firstElementChild;
el.className += 'router-link-active';
}
However vue is not removing the class again when I click on the link to /b or /c. Can any of you hint me in the corrent direction?
You can manually bind class to router-link like this
<router-link :class="{'router-link-active': $route.fullPath ==='/' || $route.fullPath === '/a'}" to="/a"></router-link>
The best solution is use "exact" in root url
<router-link to="/" tag="a" exact>Home</router-link>
Try <router-link to="/" exact>
It prevents active class inclusive match behavior.
This is my pre-answer - see solution instead.
I ended up doing something custom, rather than seeking a native solution. After looking at a few issues on GitHub, I realised that something like this, stupid as it is - I admit, doesn't exist natively in Vue.js.
My solution: adding an eventlistener that removes itself along with the class, once another link is clicked in the #drawerMenu:
HTML structure:
<ul id="drawerMenu">
<li>
<router-link to="/a">A</router-link>
</li>
<li>
<router-link to="/b">B</router-link>
</li>
<li>
<router-link to="/c">C</router-link>
</li>
</ul>
JS:
if(window.location.hash === '#/') {
var menu = document.querySelector('#drawerMenu');
var el = menu.firstElementChild.firstChild;
el.className += 'router-link-active';
var removeRouterClass = function () {
el.classList.remove('router-link-active');
menu.removeEventListener('click', removeRouterClass);
}
menu.addEventListener('click', removeRouterClass);
}
I hope this helps others like me, and hopefully this will be built into Vue natively at some point.
In my case I had a use-case when I needed to have an active class whenever 1 of the following is true:
/users
/users/1/edit
/users/1/view
using vuejs 3 in main app.vue I have routes defined. The routes are then parameter-injected into sidemenu component (which is not important in this example)
app.vue dependencies
import { ref, watch } from "vue";
import { useRoute } from "vue-router";
setup() method (using composition API):
setup() {
const route = useRoute();
const myActiveClass = ref(null);
const checkIfMustActivateRoute = () => {
return {
active:
window.location.pathname.startsWith("/users/view/") ||
window.location.pathname.startsWith("/users/edit/"),
};
};
watch(
() => route.fullPath,
async (_) => {
myActiveClass.value = checkIfMustActivateRoute();
}
);
myActiveClass.value = checkIfMustActivateRoute();
const routes = ref([
{ id: 0, name: "Home", href: "/", icon: "fas fa-home" },
{
id: 1,
name: "Users",
href: "/users",
icon: "far fa-clone",
class: myActiveClass,
},
]);
return {
routes,
};
},
NavLinks are dynamically generated like so:
<li v-for="tab of routes" :key="tab.id" class="nav-item">
<router-link :to="tab.href" class="nav-link" :class="tab.class">
<div class="align-items-center">
<span class="nav-link-icon"><i :class="tab.icon"></i></span>
<span class="nav-link-text ms-3">{{ tab.name }}</span>
</div>
</router-link>
</li>
Now whenever the router's path property changes I trigger the active class update function. This way we don't have to use aliases or any other trickery.
The result is - for all 3 paths I always enable active class.
use could use exact-active-class on your router-link element.
Note: desiredClass is the class you want to apply if the URL matches the router-link's to attribute.
<router-link to="/about" exact-active-class="desiredClass">About</router-link>

Categories