MDS Assistant is an experiment.
Hi, I'm the MDS Assistant and can help you with information from our guidelines and components.

I do not provide code and development guidance. For coding assistance, please check out our experimental MCP server.

Using router links with MDS components

Did you know that you can now use your framework's client-side routing solution with MDS components?
Aneta Chodor-Kjeldsen
Software Engineer
12 September 2024

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.

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:

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.

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 :id is 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, the TabBarRouter component will be displayed. In the example above, we called our component TabBarRouter, 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.

More examples of using router links in Vue

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 /tabbarrouter and 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.

More examples of using router links in React

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 :id is 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 the TabBarRouterComponent.
  • component TabBarRouterComponent: specifies that when the user navigates to a URL that matches this path, the TabBarRouterComponent will 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.

More examples of using router links in Angular