Made typos in routes? Redirect routes with functions

Reading Time: 4 minutes

Loading

Introduction

In this blog post, I want to describe a new feature called redirect functions with routes. When defining routes in Angular, it is possible to catch and redirect a route to a different path using redirectTo. One example is to catch all unknown routes and redirect them to a 404 page. Another example is redirecting a default route to a home page. In Angular 18, redirectTo is enhanced to accept a function. Then, a route can perform logic in the function and route to different paths according to some criteria.

I will demonstrate how I made typos in the routes and used the redirect routes to functions technique to redirect them to the routes with the correct spelling.

let's go

Update Application Config to add routing

// app.config.ts 

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(),
    provideRouter(routes, withComponentInputBinding()),
    provideExperimentalZonelessChangeDetection()
  ]
};

provideRouter(routes, withComponentInputBinding()) add routes to the demo to navigate to Pokemon List and Pokemon Details respectively.

// main.ts

import { appConfig } from './app.config';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <h2>redirectTo Demo</h2>
    <router-outlet />
  `,
})
export class App {}

bootstrapApplication(App, appConfig);

Bootstrap the component and the application configuration to start the Angular application.

Create routes for navigation to Pokemon list and Pokemon Details

I wanted to add two new routes to load the components but I made typos in the configuration. I typed /pokermon-list instead of /pokemon-list. Similarly, I typed /pokermon-list/:name instead of the correct spelling /pokemon-list/:name.

// app.route.ts

export const routes: Routes = [
    {
        path: 'pokermon-list',
        loadComponent: () => import('./pokemons/pokemon-list/pokemon-list.component'),
        title: 'Pokermon List',
    }, 
   {
        path: 'pokemon-list/:name',
        loadComponent: () => import('./pokemons/pokemon/pokemon.component'),
        title: 'Pokermon',
    },
   {
        path: '',
        pathMatch: 'full',
        redirectTo: 'pokermon-list'
    },
    {
        path: '**',
        redirectTo: 'pokermon-list'
    }
];

The careless mistakes went unnoticed, and I copied and pasted the erroneous routes to the routeLink in the inline templates.

Add routing in the template

// pokemon-list.component.ts

div class="container">
      @for(p of pokemons(); track p.name) {
        <div class="card">
          <p>Name: <a [routerLink]="['/pokermon-list', p.name]">{{ p.name }}</a></p>
        </div>
      }
</div>

In the Pokemon List component, the first element of the routerLink is /pokermon-list.

// pokemon.component.ts

<div>
      <a [routerLink]="'/pokermon-list'">Back</a>
</div>

In the Pokemon component, the value of the routerLink is /pokermon-list to go back to the previous page.

Someone spotted the typos in the URL and informed me. I easily fixed the typos in the routerLink and the routes array.

Fix the errors in routes

// app.routes.ts

import { Routes } from '@angular/router';

export const routes: Routes = [
    {
        path: 'pokemon-list',
        loadComponent: () => import('./pokemons/pokemon-list/pokemon-list.component'),
        title: 'Pokermon List',
    }, 
    {
        path: 'pokemon-list/:name',
        loadComponent: () => import('./pokemons/pokemon/pokemon.component'),
        title: 'Pokermon',
    }, 
    {
        path: '',
        pathMatch: 'full',
        redirectTo: 'pokemon-list'
    },
    {
        path: '**',
        redirectTo: 'pokemon-list'
    }
];

All occurrences of pokermon-list were replaced with pokemon-list in the routes array.

// pokemon-list.component.ts

div class="container">
      @for(p of pokemons(); track p.name) {
        <div class="card">
          <p>Name: <a [routerLink]="['/pokemon-list', p.name]">{{ p.name }}</a></p>
        </div>
      }
</div>
// pokemon.component.ts

<div>
      <a [routerLink]="'/pokemon-list'">Back</a>
</div>

Similarly, the templates replaced /pokermon-list with /pokemon-list in the routerLink inputs.

This should work, but I wanted to improve my new solution. If someone bookmarked /pokermon-list or /pokermon-list/pikachu before, these URLs do not work now. Instead, the URLs must redirect to /pokemon-list and /pokemon-list/pikachu.

I will show you how it can be done by using the new redirectTo function in Angular 18.

Redirect routes with function

// app.routes.ts

export const routes: Routes = [
    {
        path: 'pokemon-list',
        loadComponent: () => import('./pokemons/pokemon-list/pokemon-list.component'),
        title: 'Pokermon List',
    }, 
    {
        path: 'pokemon-list/:name',
        loadComponent: () => import('./pokemons/pokemon/pokemon.component'),
        title: 'Pokermon',
    }, 
    {
        path: 'pokermon-list',
        redirectTo: 'pokemon-list',
    },
    {
        path: 'pokermon-list/:name',
        redirectTo: ({ params }) => {
            const name = params?.['name'] || '';
            return name ? `/pokemon-list/${name}` : 'pokemon-list';
        }
    },
    {
        path: '',
        pathMatch: 'full',
        redirectTo: 'pokemon-list'
    },
    {
        path: '**',
        redirectTo: 'pokemon-list'
    }
];

redirectTo accepts a string or a function; therefore, I redirected /pokermon-list to /pokemon-list. This is the same behavior before Angular 18.

The /pokermon-list/:name route was tricky because I wanted to redirect to /pokemon-list/:name when the name route parameter was present and to /pokemon-list when the name route parameter was missing. I satisfied the requirements by redirecting the route with a function.

{
        path: 'pokermon-list/:name',
        redirectTo: ({ params }) => {
            const name = params?.['name'] || '';
            return name ? `/pokemon-list/${name}` : 'pokemon-list';
        }
 },

I de-structured params from ActivatedRouteSnapshot and found the value of the name property in the record. When the string was not empty, users were directed to see the Pokemon details, and when it was empty, they were redirected to the Pokemon list.

The full listing of the routes array

// app.routes.ts

import { Routes } from '@angular/router';

export const routes: Routes = [
    {
        path: 'pokemon-list',
        loadComponent: () => import('./pokemons/pokemon-list/pokemon-list.component'),
        title: 'Pokermon List',
    }, 
    {
        path: 'pokemon-list/:name',
        loadComponent: () => import('./pokemons/pokemon/pokemon.component'),
        title: 'Pokermon',
    }, 
    {
        path: 'pokermon-list',
        redirectTo: 'pokemon-list',
    },
    {
        path: 'pokermon-list/:name',
        redirectTo: ({ params }) => {
            const name = params?.['name'] || '';
            return name ? `/pokemon-list/${name}` : 'pokemon-list';
        }
    },
    {
        path: '',
        pathMatch: 'full',
        redirectTo: 'pokemon-list'
    },
    {
        path: '**',
        redirectTo: 'pokemon-list'
    }
];

This is the full listing of the routes array where navigation and redirection work as expected.

The following Stackblitz repo displays the final results:

This is the end of the blog post that describes redirecting routes with redirect functions in Angular. I hope you like the content and continue to follow my learning experience in Angular, NestJS, GenerativeAI, and other technologies.

Resources:

  1. Stackblitz Demo: https://stackblitz.com/edit/stackblitz-starters-h1eyk9?file=src%2Fapp.routes.ts
  2. Github Repo: https://github.com/railsstudent/ng-redirectTo-demo
  3. Github Page: https://railsstudent.github.io/ng-redirectTo-demo