Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 653
Etchelon Avatar asked Sep 19 '16 12:09

Etchelon


People also ask

How do you do lazy loading in Angular 2?

To lazy load Angular modules, use loadChildren (instead of component ) in your AppRoutingModule routes configuration as follows. content_copy const routes: Routes = [ { path: 'items', loadChildren: () => import('./items/items. module'). then(m => m.

What is dynamic imports for lazy routes?

Lazy-loaded routes now use the standard dynamic import syntax instead of a custom string. This means that TypeScript and linters will be better able to complain when modules are missing or misspelled. The change in syntax will be taken care of for you if you're using the ng upgrade command to upgrade your app.

What is lazy routing in Angular?

Lazy loading is an approach to limit the modules that are loaded to the ones that the user currently needs. This can improve your application's performance and reduce the initial bundle size. By default, Angular uses eager loading to load modules.


1 Answers

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.

like image 170
Burak Tasci Avatar answered Sep 27 '22 20:09

Burak Tasci