Dynamic path based on service result

Viewed 833

I am using Angular 8 to create a WordPress theme.

I want to allow the user to select a custom permalink structure from the WP Dashboard.

Note: this is not a WP question. I am just mentioning WP for those who are familiar with it to be able to better understand the question. For those who are not familiar with it, a permalink structure is basically a set of rules to display the WP contents with friendly urls.

If a user can choose a custom permalink structure, I must set up a dynamic routing design for the angular app to work accordingly.

So, a service (does it really need to be a service?) that may be called customRoutesSvc will connect to a database and get the configuration, which will be something as follows:

{
 "posts_permalink":"\/%postname%\/",
 "tag_base":"tag",
 "category_base":"category"
}

Suppose the customRoutesSvc has those values as properties now.

So, I need to somehow feed my routing module, to consider that information.

My routing module uses lazy loading and looks like this, with fixed routing paths.

{
  path: 'category/:category',
  loadChildren: () => import('./view-category/view-category.module').then(m => m.ViewCategoryModule)
},
{
  path: 'tag/:tag',
  loadChildren: () => import('./view-tag/view-tag.module').then(m => m.ViewTagModule)
},
{
  path: 'post/:slug',
  loadChildren: () => import('./view-post/view-post.module').then(m => m.ViewPostModule)
}

I was thinking about something like:

{
  path: customRoutesSvc.category_base + 'category/:category',
  loadChildren: () => import('./view-category/view-category.module').then(m => m.ViewCategoryModule)
},
{
  path: customRoutesSvc.tag_base + '/:tag',
  loadChildren: () => import('./view-tag/view-tag.module').then(m => m.ViewTagModule)
},
{
  path: '**',
  loadChildren: () => import('./view-post/view-post.module').then(m => m.ViewPostModule)
}

Noting that the last route will absorb everything and then the view-post module will handle the rest and display the post or redirect to an error page.

Why am I having issues with this?

As far as I understand, Angular loads Angular modules before a service. Not because of any preference, but because a service is a ES2015 module that gets bundled into an Angular module. Which means: Angular needs its Angular modules to know what ES2015 modules (components, services, pipes, ...) to load. So, the Angular routing module is loaded before the service and hence, the service cannot talk with the module itself.

What did I try so far?

I tried to think and get an answer.

Please do not interpret I am here to ask for code. This is more a conceptual question that aims not for a code-answer but for a theory answer that will help me keep on on my own.

What I think is the only possible solution?

Since the service cannot talk to the routing module (is that true?), I need to make a parent routing with a single path that absorbs everything (**).

Then I need to have a DispatchComponent that will analyze that url and compare it with the already available information about the permalink structure.

Finally, I need the DispatchComponent to redirect the content to the correct final component, that will take care of providing the result.

If that is the solution, how can I make this happen with lazy loading and how can one Component delegate its render task to another Component before Angular actually renders the HTML template?. I cannot use a redirect here since that would just restart the routing problem again.

Please feel free to ask for aclarations about my purposes. Thank you.

1 Answers

I think what you're looking for is dynamic routing. It's a woefully underdocumented feature but you can actually modify the configuration of the router at runtime, for example inside the subscription that fetches the database configuration of your customRoutesSvc:

  constructor(private router: Router) {
    router.resetConfig({
      // A new routes object like the one you supply in the routing module.
    });
    // It's also possible to manipulate the config object directly.
    router.config.unshift({ path: 'page1', component: Page1Component });
  }

So your assumption that a service cannot talk to the router is false. You could have a catch all route that has a resolver that fetches the database configuration, resets the router configuration, and re-navigates the router to the same location based on the new configuration.

Routing module routes:

const routes: Routes = [
  { path: '**', component: DummyComponent, resolve: {routing: RoutingResolve}}
];

Route resolver:

import { Injectable } from '@angular/core';
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot, Resolve} from '@angular/router';

@Injectable()
export class RoutingResolve implements Resolve<any> {

  constructor(private router: Router, private routes: customRoutesSvc ) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any>|any {
    let config = this.routes.getRoutingStructure();
    this.router.resetConfig(config);
    this.router.navigate(route.url);
  }
}

Since the config is now replaced by something else, the catchall route is gone and instead we navigate around in the Angular app freely, until a hard refresh re-initializes the app.

Related