4

I have an Angular application with the structure as on the image:

enter image description here

In want to conditionally select one of the themes based on the data retrieved from the server.

const routes: Routes = [
  {
    path: '',
    loadChildren: 'app/presentation/theme1/theme1.module#Theme1Module',
    canActivate: [Theme1Guard],
  },
  {
    path: '',
    loadChildren: 'app/presentation/theme2/theme2.module#Theme2Module',
    canActivate: [Theme2Guard],
  }
];

Both theme-1 and theme-2 modules have the same routes to similar component with different layout and styles.

UPDATE 1

I tried CanActivate guards one for theme-1 and the second for theme-2. Each guard retrieves current theme name from the themeStore and compares it to the current route:

canActivate() {
    let currentTheme: string = '';
    this.themeStore.currentTheme.subscribe((themeName) => {
      currentTheme = themeName;
    });

    if (currentTheme == 'theme1') {
      return true;
    }
    else {
      return false;
    }
  }

However, this won't work because Angular router does not look for the same path after the first one was rejected by CanActivate guard.

UPDATE 2

There's an open issue in Angular repository - Load a component in a route depending on an asynchronous condition. It seems to be added to backlog a few months ago.

vter
  • 1,881
  • 1
  • 20
  • 38

3 Answers3

2

Both theme-1 and theme-2 have the same route to similar component with different layout and styles.

No lazy-loading

Create theme-1 and theme-2 routes:

{
    path: 'theme-1', component: Theme1Component,
    children: [
      {
        path: 'page', 
        component: PageComponent,
      }

    ]
},
{
    path: 'theme-2', component: Theme2Component,
    children: [
      {
        path: 'page', 
        component: PageComponent,
      }
    ]
},

With lazy loading

If they are lazy loadable, then in main route module:

const routes: Routes = [
  {
    path: '',     
    children: [
      {
        path: 'theme-1',         
        loadChildren: 'path/to/theme1#module',
      },
      {
        path: 'theme-2',         
        loadChildren: 'path/to/theme2#module',
      }

    ]
  },
  ...
];

Lazy-loading theme-1, theme-2 module routes:

theme1-routing.module:

const routes: Routes = [
    {
        path: '',
        component: Theme1Component,

        children: [
            {
              path: 'page', 
              component: PageComponent,
            },           
        ]
    }
];

theme2-routing.module:

const routes: Routes = [
    {
        path: '',
        component: Theme2Component,

        children: [
            {
              path: 'page', 
              component: PageComponent,
            },           
        ]
    }
];
Community
  • 1
  • 1
Yerkon
  • 4,548
  • 1
  • 18
  • 30
  • In both scenarios you described there is a **theme-N** in URL. As I'm asking in the question, I need my routes to look like `/page`, not `/theme-1/page`. A user won't be able to see what the current theme is from the URL, a theme type is retrieved from the database record. – vter Apr 13 '18 at 12:10
  • @SerhiiKalaida, So how will identify which theme is current? Should be some determination – Yerkon Apr 13 '18 at 12:15
  • That's the problem I'm looking for a solution to. Perhaps, I could managed this with `CanActivate` guard. This guard should accept only that route which belongs to the theme module, which name was retrieved from db. – vter Apr 13 '18 at 12:23
0

i think your route of the second module is overridden, try to put your routing in the first position in your imports for the second module:

 // in your second module
 imports: [
    routing, 
    ... // then import the others 
],
Fateh Mohamed
  • 20,445
  • 5
  • 43
  • 52
  • The the second route overrides the first one. I need both of them to work on the theme type retrieved from database. – vter Apr 13 '18 at 12:12
0

In Angular 14.1+ you can use CanMatch guards for this.

Lazy-loaded or not, if two routes share the same path there needs to be some condition to separate them. CanActivate runs only after the route is decided. With CanMatch this is simple, as it can be asynchronous, uses Angular's dependency injection like other guards, and can skip routes in the matching stage. It'll also add the functionality of CanLoad, as a route that can't be matched won't load its child routes.

Without CanMatch, you could replace path with matcher and provide a synchronous function that would return true for the right route.

Now the problem is that UrlMatcher is synchronous and has no dependency injection. There's a detailed answer on a different question about this exact problem, and it includes workarounds for pre-CanMatch Angular versions.

Briefly, one workaround is adding a state variable that will be used in both of the matchers, updating it in CanActivate, and if the new value is different - redirecting so the routes will be matched again.

You'd also need to make sure the routes are matched in the first place, or there will be cases where the current state is not updated and no route will be matched. In the question's case it's already assured, as their child routes have the same path structure, but in other cases you could add a wildcard route that has the same guard that updates the state variable.

orelby
  • 227
  • 3
  • 14