What are router links?
Client-side routing is a technique used by single-page applications (SPAs) to link the browser’s URL with the content displayed to the user. As a user navigates through the application, the URL changes accordingly, but the page does not need to be reloaded from the server. This method is a key feature of modern web development, enabling smooth navigation within a web application without needing to refresh the entire page. There are many popular open source router libraries for JavaScript frameworks to be used in SPAs, such as React Router, Vue Router, and Angular Router.
How can you use router links with MDS components?
In the past, it was not possible to use your framework’s client-side routing solution with MDS components. However, we have now made it possible to use router links with following MDS components:
Benefits of using router links with MDS components
Using router links in an application with MDS components, brings several benefits:
Improved User Experience (UX)
- Seamless Navigation: router links enable seamless, fast navigation without full page reloads, providing a smoother user experience.
- State Preservation: since the page isn’t completely reloaded, the application can maintain its state (e.g. form inputs, scroll positions) between route transitions.
Performance Optimization
- Faster Load Times: router links will only update the required parts of the page. This reduces the load on the server and improves performance.
- Reduced Server Requests: SPAs load most resources upfront, so navigating with router links avoids redundant requests to the server, improving speed.
URL Management and Bookmarking
- Dynamic URL Generation: router links allow for easy generation of dynamic and SEO-friendly URLs.
- Deep Linking: they enable deep linking, so users can share or bookmark specific states of the application, improving usability.
Back/Forward Navigation
- History API Integration: modern routers use the browser’s history API, allowing users to use the back and forward buttons without losing context, contributing to a native browser experience.
SEO and Accessibility
- Search Engine Optimization (SEO): in certain cases, frameworks with server-side rendering (SSR) or static generation can use router links while making pages crawlable by search engines, improving SEO.
- Accessibility: router links behave like traditional links for screen readers and assistive technologies, ensuring a more accessible experience if implemented properly.
Centralized Route Management
- Maintainable Navigation: using a router centralizes route management in one place, making it easier to manage and update navigation structure.
- Guard Logic: routers often provide the ability to add navigation guards (e.g. for authentication), ensuring that users only access appropriate parts of the application.
Consistent Navigation Patterns
- Uniform API: routers often provide standardized APIs to manage navigation, which leads to consistent patterns throughout the application. This is useful in large-scale applications for maintainability.
How to use router links with MDS components
Depending on the framework used in the project, and the router library that is installed in the application, the implementation of router links with MDS components may vary.
Below, we provide examples of how to use router links with MDS components in some popular frameworks. All examples use mc-tab-bar component from MDS, but the same approach can be applied to other MDS components that support router-links.
Vue & vue-router
In the router file, add the following code:
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import TabBarRouter from '../views/TabBarRouter.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/tab-bar-router/:id',
name: 'TabBarRouter',
component: TabBarRouter,
},
];
const router = createRouter({
history: createWebHistory('/'),
routes,
});
export default router;
In the router file, you can define the routes array, which contains:
- path
/tab-bar-router/:id: this sets up a dynamic route where:idis a route parameter that can be accessed by the component. - name
TabBarRouter: a unique name for this route. Naming routes is useful for programmatic navigation. - component
TabBarRouter: specifies that when this route is matched, theTabBarRoutercomponent will be displayed. In the example above, we called our componentTabBarRouter, but in you app you can use any name you like.
Next, in the TabBarRouter.vue file, add the following code:
<template>
<mc-tab-bar :currentindex="$route && $route.params && $route.params.id ? +$route.params.id : 0">
<mc-tab slot="tab">
<router-link to="/tab-bar-router/0">Tab 1</router-link>
</mc-tab>
<div slot="panel">Tab 1</div>
<mc-tab slot="tab">
<router-link to="/tab-bar-router/1">Tab 2</router-link>
</mc-tab>
<div slot="panel">Tab 2</div>
<mc-tab slot="tab">
<router-link to="/tab-bar-router/2">Tab 3</router-link>
</mc-tab>
<div slot="panel">Tab 3</div>
</mc-tab-bar>
</template>
<script lang="ts">
import '@maersk-global/mds-components-core/mc-tab-bar';
import '@maersk-global/mds-components-core/mc-tab';
export default {};
</script>
In the code above, we pass the currentindex prop to the mc-tab-bar component, which is set to the value of the current $route.params.id. This way, the tab with the corresponding index will be active when the route changes.
Inside each mc-tab, we pass our router-link, which changes the URL to i.e. for the first tab /tab-bar-router/0, triggering a route change and updating the value of $route.params.id to 0. At the same time the content of the first tab is displayed.
React & React Router
In the router file, add the following code:
import { StrictMode } from 'react';
import * as ReactDOM from 'react-dom/client';
import { BrowserRouter, Link, Routes, Route } from 'react-router-dom';
import TabBarRouter from './app/TabBarRouter';
import App from './app/app';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/tabbarrouter">
<Route path=":id" element={<TabBarRouter />} />
</Route>
</Routes>
</BrowserRouter>
</StrictMode>
);
In the router file render method, you can define the routes, that contains:
StrictMode: wraps the entire application in React’s strict mode. It will help you to catch unexpected side effects and potential issues by running some checks twice (like rendering components and detecting deprecated features).BrowserRouter: this component wraps the app and enables routing functionality. It listens to the browser’s URL changes and matches routes to components.Routes: defines the available routes in the application.Route: defines a parent route for/tabbarrouterand its nested routes:path=":id". This nested route specifies a dynamic segment:id. The colon (:) means that id is a route parameter that can vary (e.g.,/tabbarrouter/1,/tabbarrouter/2, etc.).element={<TabBarRouter />}will be rendered, when the route matches/tabbarrouter/:index.
Next, in the TabBarRouter.tsx component, add the following code:
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import { McTab } from '@maersk-global/mds-react-wrapper/components-core/mc-tab';
import { McTabBar } from '@maersk-global/mds-react-wrapper/components-core/mc-tab-bar';
const TabBarRouter = () => {
let { id } = useParams();
const [currentIndex, setCurrentIndex] = React.useState<number>(id ? +id : 0);
return (
<div>
<McTabBar currentindex={currentIndex}>
<McTab onClick={() => setCurrentIndex(0)} slot="tab">
<Link to="/tabbarrouter/0">
Tab 1
</Link>
</McTab>
<div slot="panel">Tab 1</div>
<McTab onClick={() => setCurrentIndex(1)} slot="tab">
<Link to="/tabbarrouter/1">
Tab 2
</Link>
</McTab>
<div slot="panel">Tab 2</div>
<McTab onClick={() => setCurrentIndex(2)} slot="tab">
<Link to="/tabbarrouter/2">
Tab 3
</Link>
</McTab>
<div slot="panel">Tab 3</div>
</McTabBar>
</div>
);
};
export default TabBarRouter;
In the code above, we use the useParams() hook to retrieve the id parameter from the current URL. For example, if the user visits /tabbarrouter/0, id will be 0. The currentIndex state keeps track of the active tab, which is initially set to id from the URL if it exists, or 0 by default.
Inside each McTab, we use the Link component from React Router to create a router link.
When the user clicks on the link, the URL changes to i.e. /tabbarrouter/0, triggering a route change and updating the value of currentIndex to 0. At the same time, the content of the first tab is displayed.
Angular & Angular Router
In the router file, add the following code:
import { NgModule } from '@angular/core';
import { RouterModule, Route } from '@angular/router';
import { TabBarRouterComponent } from './tab-bar-router/tab-bar-router.component';
const appRoutes: Route[] = [
{ path: 'tabbarrouter/:id', component: TabBarRouterComponent },
];
@NgModule({
imports: [RouterModule.forRoot(appRoutes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
In the router file, you can define the routes, that contains:
- path
tabbarrouter/:id: this defines the URL path for the route. The:idis a dynamic route parameter, meaning this part of the URL can vary (e.g.,/tabbarrouter/1,/tabbarrouter/2, etc.). The value of id will be accessible to theTabBarRouterComponent. - component
TabBarRouterComponent: specifies that when the user navigates to a URL that matches this path, theTabBarRouterComponentwill be rendered.
Next, in the tab-bar-router.component.ts component, add the following code:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-tab-bar-router',
templateUrl: './tab-bar-router.component.html',
styleUrls: ['./tab-bar-router.component.scss'],
})
export class TabBarRouterComponent implements OnInit {
public id$: Observable<string | null> | undefined;
public currentIndex$?: BehaviorSubject<number> = new BehaviorSubject(0);
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.id$ = this.route.paramMap.pipe(map((params: ParamMap) => params.get('id')));
this.id$.subscribe((param) => {
this.currentIndex$?.next(param ? +param : 0);
});
}
}
In the code above, id$ is an Observable that emits the value of the id route parameter and is initialized in the ngOnInit method.
currentIndex$ is a BehaviorSubject that holds and emits the current index.
This value gets updated whenever the id$ observable changes. The subscribe() method listens for these changes in the id$ observable, and whenever a new id is emitted, it updates the currentIndex$ value accordingly.
Lastly, in the tab-bar-router.component.html component, add the following code:
<mc-tab-bar [currentindex]="currentIndex$ | async">
<mc-tab slot="tab"><a routerLink="/tabbarrouter/0">Tab 1</a></mc-tab>
<div slot="panel">Tab 1</div>
<mc-tab slot="tab"><a routerLink="/tabbarrouter/1">Tab 2</a></mc-tab>
<div slot="panel">Tab 2</div>
<mc-tab slot="tab"><a routerLink="/tabbarrouter/2">Tab 3</a></mc-tab>
<div slot="panel">Tab 3</div>
</mc-tab-bar>
In the code above [currentindex]="currentIndex$ | async" is an Angular property binding. currentIndex$ is an Observable, so this expression: | async, subscribes to this observable and automatically updates the template when the value of currentIndex$ changes.