Etchelon
Etchelon

Reputation: 882

Angular 2 (Final): resetConfig to add routes to lazy loaded routes

I'm trying to understand how to dynamically add routes to those from a lazy loaded module. I have the core app, with routes:

export const routes = [{
    path: "",
    component: HomeComponent
}, {
    path: "moduleA",
    loadChildren: "app/moduleA/A.module"
}];

In A.module I import routes from the "local" moduleA.routes.config.ts:

const routes = [{
    path: "",
    component: ModuleAHomeComponent,
    children: [
        { path: "", component: ModuleAListComponent },
        { path: ":id", component: ModuleADetailComponent }
    ]
}];
export const routing = RouterModule.forChild(routes);

So far so good. Now, in ModuleADetailComponent I have a list of "additional modules", which I'll call plugins, for my webapp, which are developed separately and I want to be able to show in ModuleADetailComponent page. Therefore, the template for this component is something like this:

<div *ngFor="let plugin of plugins">
    <div class="header">{{ plugin.name }}</div>
    <router-outlet name="*plugin.name*">*LOAD MODULE HERE*</router-outlet>
</div>

Of course, plugins is a configuration loaded upon navigation to ModuleADetailComponent, so I don't know ahead of time which modules are configured and how many there are. Lastly, each of these plugins can be an NgModule (developed separately in a workbench environment).

What I need is: how do I modify the router configuration, dynamically, to add children to the route defined in moduleA.routes.config.ts? For example, if I have 2 plugins, I want to dynamically use router.resetConfig to have what I would have if moduleA.routes.config.ts was like this:

const routes = [{
    path: "",
    component: ModuleAHomeComponent,
    children: [
        { path: "", component: ModuleAListComponent },
        { 
            path: ":id",
            component: ModuleADetailComponent,
            children: [
                {
                    path: "plugin1",
                    loadChildren: "app/plugins/plugin1.module",
                    outlet: "plugin1"
                },
                {
                    path: "plugin2",
                    loadChildren: "app/plugins/plugin2.module",
                    outlet: "plugin2"
                }                
            ]
        }
    ]
}];
export const routing = RouterModule.forChild(routes);

I hope I can achieve it, otherwise plugins will have to be developed without routing...but they can become big sub-modules which would benefit from navigation.

EDIT 27/09/2016: I've assembled this plunkr: http://plnkr.co/edit/6aV5n5K5wdqdiYo49lZv. What I am currently able to do is load components dynamically and add them to the application (see tab "Dynamic Modules"). What I've also done is to use multiple named router-outlets, despite not finding any official documentation (see tab "Dynamic Routed"). Despite the tab name, routes are known ahead of time.

What I want to do is: dynamically load a configuration (the dynamicRoutes array in the DynamicRoutesComponent) and only after create one router-outlet per dynamic route and use resetConfig (or what it takes) to have this configuration:

{
  path: "dynamic-routed",
  component: DynamicRoutesComponent,
  children: [
    { path: "", component: EmptyComponent },
    { path: "component1", component: Component1, outlet: "component1" },
    { path: "component2", component: Component2, outlet: "component2" }
  ]
}

with one child per dynamic route.

Thanks in advance!

Upvotes: 5

Views: 6753

Answers (3)

AntuJitsu
AntuJitsu

Reputation: 119

Actually the problem to me is how costructing newChildRoute:

before I had something like:

         "path": pathFromFile,
         "outlet": outletFromFile,
         "data": dataFromFile,
         "component": HostComponent, (my custom host component for named outlet)
         "loadChildren": moduleURLFromFile + "#" + moduleNameFromFile

to create new objects {} to change my actual this.router.config.

in this way calling this.router.resetConfig(this.router.config); should work....

but replacing "loadChildren": moduleURLFromFile + "#" + moduleNameFromFile

with

"loadChildren": () => import(moduleURLFromFile ).then(m => m.moduleNameFromFile)

seems not working.

hope this better clarify

Upvotes: 0

cjbarth
cjbarth

Reputation: 4479

Would it be possible to do something like this? Basically, in the constructor of a component you have access to activatedRoute.routeConfig.children, with which you should be able to add more children to that array.

If you are trying to get data from another source, you should be able to set this up in the subscriber part of an Observable. I'm thinking something like this:

@Component({
  selector: 'app-module-a-detail',
  templateUrl: './module-a-detail.component.html'
})
export class ModuleADetailComponent {
  constructor(
    private activatedRoute: ActivatedRoute) {

    const childRoutes: Routes = this.activatedRoute.routeConfig.children;

    this.pluginService.getPlugins({
      .subscribe(map(plugin => {
        // build up my additional childRoute here
        childRoutes.push(newChildRoute);
      }))
    );
  }
}

Upvotes: 1

Burak Tasci
Burak Tasci

Reputation: 907

First of all, the loadChildren property method must be supplied with the following format (https://angular.io/docs/ts/latest/guide/router.html#!#asynchronous-routing):

{
  path: 'admin',
  loadChildren: 'app/admin/admin.module#AdminModule',
},

As you see above, the module path and the module name are joined with the # char.

In your case, you need to to modify your app.routes more or less as follows (not sure about your module/file names, and the plunkr sample does not reflect the names given in this question):

export const routes = [{
    path: "",
    component: HomeComponent
}, {
    path: "moduleA",
    loadChildren: "app/moduleA#ModuleA"
}];

This was just an intro, now let's proceed to the solution of your problem. As far as I read, you want to dynamically add child routes.

You can dynamically provide child routes in your feature module by using the following code.

import {ROUTES} from '@angular/router/src/router_config_loader';
import {ANALYZE_FOR_ENTRY_COMPONENTS} from '@angular/core';

[...]
let routesToAdd = ... // provide your routes here

@NgModule({
  [...]
  imports: [RouterModule.forChild([])] // Empty on purpose
  [...]
  providers: [
    {
      provide: ROUTES,
      useValue: routesToAdd,
      multi: true
    },
    {
      provide: ANALYZE_FOR_ENTRY_COMPONENTS,
      useValue: routesToAdd, // provide them here too
      multi: true
    }
  ],
  [...]
})

You had to provide same routes for ANALYZE_FOR_ENTRY_COMPONENTS token (as well as ROUTES token) otherwise you'll get the No component factory found for YOUR_COMPONENT. Did you add it to @NgModule.entryComponents? error.

However, this code might produce errors during AoT compilation. In this case, you'll need to provide a factory instead of a value for ROUTES token. However, you won't be able to provide a factory for ANALYZE_FOR_ENTRY_COMPONENTS token - it does only accept useValue (see https://github.com/angular/angular/blob/03e855ae8f9e9b86251320c7551a96092bd8b0c4/modules/%40angular/compiler/src/metadata_resolver.ts#L926).

In that case, use the following code on your feature module:

import {ROUTES} from '@angular/router/src/router_config_loader';

[...]
let routesToAdd = ... // provide your routes here

@NgModule({
  [...]
  imports: [RouterModule.forChild([])] // Empty on purpose
  [...]
  providers: [
    {
      provide: ROUTES,
      useFactory: (getRoutesFromSomewhere),
      //deps: [SomeService, SOME_TOKEN] // deps is optional, in the case you need no params - delete it; otherwise pass 'em
      multi: true
    }
  ],
  entryComponents: [CompA, CompB, CompC, etc...]
  [...]
})

Note: Using Observables/Promises to provide routes is not a reliable solution, hence the Angular router expects Route[] or Routes, but an HTTP request can only return an Observable/Promise (The Angular app gets initialized, but the retrieval process of routes still goes on using Observables/Promises) (see https://github.com/ngx-i18n-router/config-loader#readme).

Use such functions which return Route[] or Routes.

Meantime, you might check the module file of @ngx-i18n-router/core (an internationalization utility for Angular, benefits from dynamic routes) and this example-app as its implentation.

Upvotes: 9

Related Questions