Navigation Lifecycle using Vaadin Router, LitElement and TypeScript
Tags: litelementtypescript
Published: 2020-09-22 13:25:58

---
title: Navigation Lifecycle using Vaadin Router, LitElement and TypeScript
published: true
date: 2020-09-21 13:10:23 UTC
tags: litelementtypescript
canonical_url: https://www.thisdot.co/blog/navigation-lifecycle-using-vaadin-router-litelement-and-typescript
---

In a previous post, I explained the main aspects to add a [Routing Management with LitElement and TypeScript](https://labs.thisdot.co/blog/routing-management-with-litelement) to build a SPA(_Single Page Application_). It's important to note that a good understanding of the routing library you selected to work through can help you to have more control over the navigation lifecycle.

### Navigation Lifecycle

Creating and configuring routes into your Web Application is only one part of the _Routing Management_ story.

[![lifecycle-callbacks](https://images.ctfassets.net/zojzzdop0fzx/2NtawyEvJCkT92gkS0auVx/858b804c56f56e573c999645d2253a18/lifecycle-callbacks.jpg)](https://images.ctfassets.net/zojzzdop0fzx/2NtawyEvJCkT92gkS0auVx/858b804c56f56e573c999645d2253a18/lifecycle-callbacks.jpg)

As you can see, an instance of the class will be created first (using the _constructor_). Let's walk around the Lifecycle functions we can use to have full control over navigation using [Vaadin Router](https://vaadin.com/router) and [LitElement](https://lit-element.polymer-project.org/) for Web Components.

### onBeforeEnter

Let's suppose we have an Admin page, which is displayed only for authorized users. Our Web Component will look as follows:

```
import {
  PreventAndRedirectCommands,
  Router,
  RouterLocation,
} from '@vaadin/router';
import { LitElement, html, customElement } from 'lit-element';

@customElement('lit-admin')
export class Admin extends LitElement {
  render() {
    return html`
      <h2>Admin</h2>
      <p>Only for authorized users</p>
    `;
  }
}
```

However, this page will remain accessible to everyone unless you define a _rule_ to avoid loading it for restricted users.

[![01-initial-screenshot](https://images.ctfassets.net/zojzzdop0fzx/StCSyPAW8e5o4AyNSAUMh/508d242730a35ead1730a70d0b6d2f4d/01-initial-screenshot.png)](https://images.ctfassets.net/zojzzdop0fzx/StCSyPAW8e5o4AyNSAUMh/508d242730a35ead1730a70d0b6d2f4d/01-initial-screenshot.png)

If you're working with Vaadin Router and Web Components, you don't need to _implement_ or _extend_ from any class. It's enough to define a function name that matches to any lifecycle functions.

So, it's time to implement a **route guard** in order to restrict the access to this page and do a redirect instead:

```
  public onBeforeEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): RedirectResult | undefined {
    if(!this.isAuthorized()) {
      return commands.redirect('/'); // Redirects to home page
    }
  }

  private isAuthorized() {
    // Logic to determine if the current user can see this page
    return false;
  }
```

In this case, the _onBeforeEnter_ function returns a _RedirectResult_ as a router command. That means the router will perform a redirection to `/`(the home page) before loading the current path.

If you need to perform any _async_ action in this context, then you can return a _Promise_ instead:

```
  public onBeforeEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): Promise<unknown> | undefined {
    if (!this.isAuthorized()) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(commands.redirect('/'));
        }, 2000);
      });
    }
  }
```

In the previous example, the router will wait until the _Promise_ is resolved before continuing to load the page.

Find below the complete TypeScript class implementation:

```
import {
  PreventAndRedirectCommands,
  RedirectResult,
  Router,
  RouterLocation,
} from '@vaadin/router';
import { LitElement, html, customElement } from 'lit-element';

@customElement('lit-admin')
export class Admin extends LitElement {
  render() {
    return html`
      <h2>Admin</h2>
      <p>Only for authorized users</p>
    `;
  }

  public onBeforeEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): Promise<unknown> | RedirectResult | undefined {
    console.log('onBeforeEnter');
    if (!this.isAuthorized()) {
      // sync operation
      // return commands.redirect('/');

      // async operation
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          console.log('Not authorized, redirect to home page');
          resolve(commands.redirect('/'));
        }, 2000);
      });
    }

    console.log('You can see this page');
  }

  private isAuthorized() {
    // Logic to determine if the current user can see this page
    return false;
  }
}
```

### onAfterEnter

This function can be used to process the _URL params_ or even initialize the page.Any value you may return will be ignored.

```
@customElement('lit-admin')
export class Admin extends LitElement {

  @property({ type: String }) username!: string;

  render() {
    return html`
      <h2>Admin</h2>
      <p>Welcome ${this.username}</p>
      <p>Only for authorized users</p>
    `;
  }

  public onAfterEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): void {
    console.log('onAfterEnter');
    // Read params from URL
    const section = location.params.section; // path: 'admin/:section'
    const username = new URLSearchParams(location.search).get('username');
    console.log('section', section);
    console.log('username', username);

    // Assign the username value from the URL
    this.username = username || 'user';
    // No need to return a result.
  }
}
```

As you can see, this function will be in charge of all the needed initialization of the page.

As the first step, it will read the parameters from the URL: `http://localhost:8000/admin/profile?username=luixaviles`. Then the `username` value will be assigned to an existing property.

Here you have the output screen:

[![02-on-after-enter](https://images.ctfassets.net/zojzzdop0fzx/1yXEefCtE3oTzVkv2dMjr6/9f621084d7e76bcefd7677354aac2997/02-on-after-enter.png)](https://images.ctfassets.net/zojzzdop0fzx/1yXEefCtE3oTzVkv2dMjr6/9f621084d7e76bcefd7677354aac2997/02-on-after-enter.png)

### onBeforeLeave

This function will be executed once the current path doesn't match anymore. That means the page or component is about to be removed from the DOM.

```
  public onBeforeLeave(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): PreventResult | undefined {
    console.log('onBeforeLeave');

    const leave = window.confirm('Are you sure to leave this page?');
    if (!leave) {
      return commands.prevent();
    }
  }
```

[![03-on-before-leave](https://images.ctfassets.net/zojzzdop0fzx/nJrjvJ4TTDIGSdmddpsER/194b3b9763e8539a5c50fec812ccf770/03-on-before-leave.png)](https://images.ctfassets.net/zojzzdop0fzx/nJrjvJ4TTDIGSdmddpsER/194b3b9763e8539a5c50fec812ccf770/03-on-before-leave.png)

In this example, the application will show a confirm popup before leaving the current page. If the user decides to cancel the action, the function will return a `PreventResult` object due to `return commands.prevent()` call.

In case you need to perform an _async_ operation, the function can return a _Promise_ as follows:

```
  public onBeforeLeave(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): PreventResult | Promise<unknown> | undefined {
    console.log('onBeforeLeave');

    const leave = window.confirm('Are you sure to leave this page?');
    if (!leave) {
      // sync operation
      // return commands.prevent();

      // async operation
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          // console.log('Not authorized, redirect to home page');
          console.log('resolved');
          resolve(commands.prevent());
        }, 2000);
      });
    }
  }
```

### onAfterLeave

This is pretty similar to the previous one and it will be executed once the current path doesn't match anymore AND the process to remove it from the DOM has been started. Any value returned by this function will be ignored.

```
  public onAfterLeave(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): void {
    console.log('onAfterLeave');
    alert('Just wanted to say goodbye!');
  }
```

[![04-on-after-leave](https://images.ctfassets.net/zojzzdop0fzx/3y4Oq4WJ6aflNMx7MScfn3/23d20e9fb51296533935f1372efbcf13/04-on-after-leave.png)](https://images.ctfassets.net/zojzzdop0fzx/3y4Oq4WJ6aflNMx7MScfn3/23d20e9fb51296533935f1372efbcf13/04-on-after-leave.png)

As you can see in this screenshot, the URL has been changed already and the function will run as a last action from the previous view.

### The Final Component

Pay attention to all imports and types used here:

```
import {
  PreventAndRedirectCommands,
  PreventResult,
  RedirectResult,
  Router,
  RouterLocation,
} from '@vaadin/router';
import { LitElement, html, customElement, property } from 'lit-element';

import { router } from '../index';

@customElement('lit-admin')
export class Admin extends LitElement {
  @property({ type: String }) username!: string;

  render() {
    return html`
      <h2>Admin</h2>
      <p>Welcome ${this.username}</p>
      <p>Only for authorized users</p>
      <p>Go to <a href="${router.urlForPath('/about')}">About</a></p>
    `;
  }

  public onBeforeEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): Promise<unknown> | RedirectResult | undefined {
    if (!this.isAuthorized()) {
      // sync operation
      // return commands.redirect('/');

      // async operation
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(commands.redirect('/'));
        }, 2000);
      });
    }
  }

  public onAfterEnter(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): void {
    // Read params from URL
    const section = location.params.section; // path: 'admin/:section'
    const username = new URLSearchParams(location.search).get('username');
    console.log('section', section);
    console.log('username', username);

    // Assign the username value from the URL
    this.username = username || 'user';

    // No need to return a result.
  }

  public onBeforeLeave(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): PreventResult | Promise<unknown> | undefined {
    const leave = window.confirm('Are you sure to leave this page?');
    if (!leave) {
      // sync operation
      // return commands.prevent();

      // async operation
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(commands.prevent());
        }, 2000);
      });
    }
  }

  public onAfterLeave(
    location: RouterLocation,
    commands: PreventAndRedirectCommands,
    router: Router
  ): void {
    alert('Just wanted to say goodbye!');
  }

  private isAuthorized() {
    // Logic to determine if the current user can see this page
    return true;
  }
}
```

### Source Code Project

If you work through the source code of this project, use this URL for testing the _lifecycle callbacks_: [http://localhost:8000/admin/profile?username=luixaviles](http://localhost:8000/admin/profile?username=luixaviles).

Find the complete project in this GitHub repository: [https://github.com/luixaviles/litelement-website](https://github.com/luixaviles/litelement-website). Do not forget to give it a star ⭐️ and play around with the code.

You can follow me on [Twitter](https://twitter.com/luixaviles) and [GitHub](https://github.com/luixaviles) to see more about my work.