Creating dynamically generated routes in Angular or Angular 9 before initialization

I'm currently working on a project where I am in the process of converting an AngularJS application to Angular. The main challenge that I am facing at the moment revolves around routing.

To sum it up: My requirement is to define routes based on an API response prior to utilizing the routing module.

In the functioning scenario with AngularJS: (More or less pseudo code provided below)

There are some basic routes that are universally available, these are set in the standard AngularJS manner:

/home
/settings
...and so on

Additionally, there are dynamic routes generated depending on the API response

/purchase-requests
/invoices
/godzilla
...and more. The actual content isn't significant, essentially, it's a dynamic list of routes obtained from an existing API in the form of an array of strings

The fundamental procedure followed by the existing AngularJS app:

  1. The AngularJS app is NOT immediately associated with an element using ng-app as commonly done.
  2. A pure (or jQuery) response is fetched from the API upon page loading.
  3. The initialization of the AngularJS app occurs through:
 angular.bootstrap(document.getElementById('mainElementId'),['appName']);

This method functions due to AngularJS's behavior of delaying the .config() execution until the Angular app is bootstrapped, which we defer until after receiving the API response.

Here is an example of functional AngularJS code used today:

<script>

  let appList = [];
  const mainApp = angular.module('mainApp', ['ngRoute']);


  // Controllers
  mainApp.controller('mainController', mainController);
  mainApp.controller('homeController', homeController);
  mainApp.controller('appListController', appListController);
  mainApp.controller('appSingleController', appSingleController);
  mainApp.controller('errorController', errorController);

  // config will not be called until the app is bootstrapped
  mainApp.config(function($routeProvider) {

    // Default routes for all users
    $routeProvider.when('/', { templateUrl: 'views/home.html', controller: 'homeController'});
    $routeProvider.when('/home', { templateUrl: 'views/home.html', controller: 'homeController'});

    // Incorporate the dynamic routes received from the API
    for (let appName in appList) {
      $routeProvider.when(`/${appName}`, { templateUrl: 'views/app-list.html', controller: 'appListController'});
      $routeProvider.when(`/${appName}/new`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
      $routeProvider.when(`/${appName}/view/:itemNumber`, { templateUrl: 'views/app-single.html', controller: 'appSingleController'});
    }

    $routeProvider.otherwise({ templateUrl: 'views/error.html', controller: 'errorController'});
  });



  $(document).ready(function() {
    const options = {
      type: 'GET',
      url: '/api/apps/getAvailableApps',
      success: onAppSuccess,
    };
    $.ajax(options);
  });

  function onAppSuccess(response) {
    appList = response.appList;
    angular.bootstrap(document.getElementById('mainApp'), ['mainApp']);
  }

</script>

<!-- Typically, you bind to the app using ng-app="mainApp" -->
<div id="mainApp" class="hidden" ng-controller="mainController">

  <!-- Route views -->
  <div ng-view></div>

</div>

In Angular 9 (or seemingly any recent version of Angular), routes are defined within the routing module before initializing the main component:

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', component: DashboardComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
];

Using router.resetConfig doesn't provide a solution

If I have the main module load the API configuration first and then employ resetConfig based on the response, everything works fine if the user initially navigates to /, /home, or one of the other predetermined routes: The new dynamic routes are formed and navigation to them proceeds smoothly.

However, if a user directly navigates to a route that isn't predefined (for instance, /godzilla), the router prevents the page from loading altogether (or) if the wildcard route is set, presents the 404 error. The ngOnInit() function in the main component (which I intended to use to fetch the API response) never gets executed.

The question remains: How can I establish routes based on the API response before executing or even initializing the router navigation?

Answer №1

To create dynamic routes, I utilize predefined route url templates with parameters.

const routes: Routes = [
  { path: 'login', component: LoginComponent },
  { path: '', component: DashboardComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: ':appName', canActivate: AppGuard, children: [
    { path: '', component: AppListComponent },
    { path: 'new', component: 'NewAppComponent' },
    { path: 'view/:itemNumber', component: AppSingleComponent }
  ] },
  { path: '**', component: ErrorComponent }
];

Routes are prioritized based on order, so it's important to list "known" routes first. If a URL has a single segment that doesn't match any of the "known" routes, it will then be matched against :appName. A guard can be implemented to validate the :appName parameter. In case of invalid parameter, the '**' route will be triggered.

The guard implementation would resemble:

@Injectable({ providedIn: 'root' })
export class AppGuard implements CanActivate {
  constructor(private appService: AppService) {
  }

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    const appName = route.params.appName;
    return this.appService.isValidAppName(appName);
  }
}

The function appService.isValidAppName is responsible for validating the app name.

Answer №2

Is there a way to generate routes based on the API response before the router navigation takes place or is even initialized?

There are two approaches to achieve this.

The first approach involves using Components to handle dynamic routes. Static routes are defined initially, and dynamic routes are directed to the DynamicComponent with the routing parameter id. Within the DynamicComponent, ActivatedRoute is used to retrieve the routing parameter, and Router is utilized to navigate to the 404 route in case of failure.

In the app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: "prefix" },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: '404', component: PageNotFoundComponent },
  { path: ':id', component: DynamicComponent },
];

In the DynamicComponent

constructor(private aroute: ActivatedRoute, private router: Router) { }

ngOnInit(): void {

  this.aroute.params.pipe(first()).subscribe((param) => {
    console.log(param.id)

    ...   // perform any necessary API call with param.id and obtain a response as a promise

    .then( (response) => {

       ...    // implement desired actions upon successful response

    })
    .catch( (error) => {

       console.error(error);
       this.router.navigate(['404']);    // redirect to 404 page if unsuccessful

    })


  }
}

The second approach entails utilizing Services to filter out unknown routes. Similar to the first method, static routes are established first followed by dynamic routes routed to the DynamicComponent and filtered through the DynamicRouteService which implements CanActivate. In the DynamicRouteService, we map the next.params to return an Observable<boolean> to the Router module, which will hold the routing until the observable is fulfilled.

In the app-routing.module.ts

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: "prefix" },
  { path: 'login', component: LoginComponent },
  { path: 'home', component: DashboardComponent },
  { path: 'settings', component: SettingsComponent },
  { path: '404', component: PageNotFoundComponent },
  { path: ':id', component: DynamicComponent, canActivate: [DynamicRouteService] },
];

Note: Ensure to add DynamicRouteService to providers in app.module.ts

In the dynamic-route.service.ts

export class DynamicRouteService implements CanActivate {

  constructor(private router: Router) { }

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {

    return next.params.pipe(first()).pipe(map(param) => {
      console.log(param.id)

      return ...   // make any API call with param.id and get a response as promise

      .then( (response) => {

         ...    // do whatever you want to do on success
         return true;

      })
      .catch( (error) => {

         console.error(error);
         this.router.navigate(['404']);    // route to 404 on failure
         return false;

      }))

    }
  }
}

Answer №3

Appreciate the helpful responses.

I was able to resolve this issue in a different manner.

Initially, I was considering implementing a "DynamicRouter" component, but eventually discovered a more straightforward solution using APP_INITIALIZER.

You can find my detailed explanation here: Angular load routing from REST service before initialization

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Navigating Bootstrap: Refreshing a full-screen modal with a swipe gesture on mobile devices

I'm currently utilizing Bootstrap 5's full screen modal and I'm exploring how to implement a feature that enables refreshing on mobile devices by swiping down, similar to how you would usually refresh a page. <script src="https://cdn.j ...

Issue with publishing npm package using yarn package manager

I'm currently in the process of releasing a fresh package. Utilizing ES6, I've been transpiling my files through babel to start with. However, I've hit a roadblock at this particular stage: https://i.stack.imgur.com/iIVp6.png This part se ...

Creating a dataset for D3 in JavaScript: A step-by-step guide

I am attempting to construct a graph similar to this: https://i.sstatic.net/USdyj.png. The graph represents a dependencies tree where each node has a list of elements it depends on as children (in a list), or a simple JSON structure with name and size info ...

Ensuring the validity of float and non-empty values in JavaScript

Embarking on the journey of web development, I'm delving into basic applications. My current project involves creating a straightforward webpage to add two numbers and implementing preliminary validations - ensuring that the input fields are not left ...

Managing memory and CPU resources in NodeJS while utilizing MongoJS Stream

Currently, I am in the process of parsing a rather large dataset retrieved from MongoDB, consisting of approximately 40,000 documents, each containing a substantial amount of data. The dataset is accessed through the following code snippet: var cursor ...

Using AngularJS to toggle between two select dropdowns

I have two drop-down lists containing JSON data. <select class="form control" ng-model="fruitsName" ng-options="r.id as r.name for r in fruits"> <option value="">--Select---</option></select> $scope.fruits = [{'id': &apo ...

Dividing a select option

Looking to transform a single select element into multiple select elements using the separator "/" Here is an example of the original code: <select> <option value="1234">Type 1 / Black</option> <option value="5678">Type 2 / White& ...

What causes the timer to pause, and what steps can be taken to avoid it? (Using Javascript with Iframe)

On my website, I have a page where clients must view an advertisement for 20 seconds. The website is displayed in an iframe with a countdown timer above it. I've set it up so that the timer stops when the window loses focus to ensure the client is ac ...

When running the test, the message "Unable to resolve all parameters for BackendService" is displayed

Upon executing the ng test command, the following error was displayed. This is my service specification: describe('BackendService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ { p ...

JavaScript - Display all comments

Here's the structure of my JSON data: { "comment_ds": [ { "c_user": [ "Alice", "Alice", "Alice", "Alice" ...

Example of a chat server code using NodeJS

I'm looking to add a chat module to my web application. After searching on , I found several chat modules but none of them have clear examples on how to implement them in my project. Can someone share a tutorial that is based on existing NodeJS modul ...

Updating parent scope data from within a directive without relying on isolated scope bindings

What is the best method for passing data back to the parent scope in AngularJS without using isolated scopes? Imagine I have a directive called x, and I want to access its value named a. The desired syntax would be: <x a="some.obj.myA"></x> c ...

How can I pass arguments from a Python command line program (compiled to an EXE) to a JavaScript file?

As I work on developing a node program, I've come across certain abilities that Python possesses which JavaScript lacks, such as utilizing Python-specific modules. To bridge this gap, I made the decision to compile Python files into EXE and then invok ...

Extend the row of the table according to the drop-down menu choice

I am working on a feature where a dropdown menu controls the expansion of rows in a table. Depending on the option selected from the dropdown, different levels of items need to be displayed in the table. For example, selecting level 1 will expand the first ...

The onProgress event of the XMLHttpRequest is triggered exclusively upon completion of the file upload

I have a situation with my AJAX code where the file upload progress is not being accurately tracked. The file uploads correctly to the server (node express), but the onProgress event is only triggered at the end of the upload when all bytes are downloaded, ...

Display or conceal elements using the unique identifier selected from a dropdown menu in JavaScript

I have been searching the internet for a solution to my issue but nothing seems to be working. Here is the problem: Unfortunately, I cannot modify the TR TD structure and am unable to use DIVs. I am trying to dynamically display certain TD elements based ...

Is it possible to prevent a webpage from being refreshed?

I need help with an HTML page that puts the worker on pause time which will be subtracted from his/her day job. The issue is that when he/she refreshes the page, the timer starts from the beginning automatically. I want it to continue without resetting. Is ...

Leveraging JavaScript Functionality with ASP.NET Identity Roles

I'm currently working on an application that utilizes JQuery DataTables. The goal is to have these tables visible to all users, but restrict the click functionality to a specific user role. One way I can achieve this is by setting up authorization on ...

Is it possible for me to connect an npm run command as a task in a Gruntfile?

In the root directory of my site, I have made changes to the package.json file by adding the "scripts" hook below: "scripts": { "ngDeployDev": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ../../Scripts/ng-build-deploy/ngDeployDev.p ...

The @Prop property decorator in Vue cannot be utilized as it is not compatible

I have a Vue 2 component with TypeScript: import Vue from 'vue'; import Component from 'vue-class-component'; import Prop from 'vue-property-decorator'; @Component({ template: require('./template.html'), }) expo ...