Dynamic Route Loading in Angular 2 Fails. (Beta)

2019-02-15 16:23发布

I want to load routes for the @RouteConfig Dynamically from a service which fetches in format JSON,

[
  { "path" : "/about" , "name" : "About" , "component" : "AboutComponent" },
  { "path" : "/contact" , "name" : "Contact" , "component" : "ContactComponent" }
]

Following is the code for pushing it into the RouteDefinition Array,

for (let i = 0; i < data.length; i++) {
  //console.log(data[i].path+" "+data[i].name+" "+data[i].component);
  this.routeConfigArray.push({ //routeConfigArray : RouteDefinition
    'path': data[i].path,
    'name': data[i].name,
    'component': data[i].component
  });
  this._router.config(this.routeConfigArray);   // THIS FAILS TO CONFIG PATHS ON ROUTER
}

The 'component':data[i].component requires Classname, where as it recieves a it via a String class. How do i convert the string containing classname into a class??

Also i have tried the Route class using :

this.routeConfigArray.push(new Route({path: data[i].path, name: data[i].name, component:data[i].component}));

Console Error:

Component for route "/about" is not defined, or is not a class. i have tried numerous ways like using eval("new "+data[i].component+"()); || new window[data[i].component] .

I am stuck at this and really confused as to how to resolve this.

4条回答
趁早两清
2楼-- · 2019-02-15 16:30

You could use the another approach that the one from @Pierre and @Pratik based on a method that returns the name of classes:

Object.prototype.getName = function() { 
  var funcNameRegex = /function (.{1,})\(/;
  var results = (funcNameRegex).exec((this).constructor.toString());
  return (results && results.length > 1) ? results[1] : "";
};

In your component you can then configure your routes dynamically like this:

ngOnInit() {
  this.routes = [
    {
      path: '/test', component: 'OtherComponent', name: 'Test'
    }
  ];
  this.configureRoutes(this.routes);
  this.router.config( this.routes);
}

configureRoutes(routes) {
  var potentialComponents = [ OtherComponent ];
  routes.forEach((route) => {
    route.component = potentialComponents.find((component) => {
      return component.name === route.component;
    });
  });
}

This requires to know by advance potential components that can be involved in routing.

See this plunkr for demo: https://plnkr.co/edit/KKVagp?p=preview.

See this question:

查看更多
该账号已被封号
3楼-- · 2019-02-15 16:34

You are too good @PierreDuc , i was just looking at regex to build the same function, some edits i would like to point out to bring it in working state....

for (let i = 0; i < data.length; i++) {
  this.routeConfigArray.push({ //routeConfigArray : RouteDefinition
    'path': data[i].path,
    'name': data[i].name,
    'component': getComponent(data[i].component).constructor
  });
  this._router.config(this.routeConfigArray);   
}

function getComponent(comp : string) : Function {
    //convert camelCase to underscore notation
    let component : string = comp;
    component = component[0].toLowerCase() + component.slice(1);
    component = component.replace(/([A-Z])/g, function(match) {
        return '_' + match.toLowerCase();
    });
    component += '_1.';
    return eval("new "+component+comp+"()")
}

Thankyou once again dude, its now in a running mode!!! Phew!

查看更多
smile是对你的礼貌
4楼-- · 2019-02-15 16:44

TypeScript compiles imports into javascript in a certain way, you can try something like this to be able to use eval() (cringe). This obviously won't work if typescript compiles differently, but it is fun to check out if this works any ways :)

for (let i = 0; i < data.length; i++) {
  this.routeConfigArray.push({ //routeConfigArray : RouteDefinition
    path: data[i].path,
    name: data[i].name,
    component: getComponent(data[i].component)
  });
  this._router.config(this.routeConfigArray);   
}

function getComponent(comp : string) : Function {
    //convert camelCase to underscore notation
    let component : string = comp;
    component = component[0].toLowerCase() + component.slice(1);
    component = component.replace(/([A-Z])/g, function(match) {
        return '_' + match.toLowerCase();
    });
    component += '_1';
    return eval(component[comp])
}

ADDENDUM

As addition to your own solution with using a AsyncRoute, I believe you actually got quite a good solution going on. Perhaps if you place all the pages in a certain way, you can extract the resource location from the path, but that is not necessary. (i mean to get from the path string /about to the resource string ./app/about/about.component shouldn't be hard with a small algorithm. But that might be something for an update.

Anyways, you can try something like this with the AsyncRoute

warning: untested code ahead

let routes : any[] = [
    { "path" : "/about" , "name" : "About" , "component" : "AboutComponent", "route": "/About" , "resource" : "./app/about/about.component" },
    { "path" : "/contact" , "name" : "Contact" , "component" : "ContactComponent", "route": "/Contact" , "resource" : "./app/contact/contact.component" }
];

routes.forEach((route : any) => {
    this.routeConfigArray.push(
        new AsyncRoute({
            path : route.path,
            loader : () => System.import(route.resource).then(m => m[route.component]),
            name : route.name
        })
    );
});

this._router.config(this.routeConfigArray);
查看更多
我命由我不由天
5楼-- · 2019-02-15 16:44

Update to the first above scenario : The above dynamically loads routes on the UI, but for that to happen i do have to mention it in my AppComponent

import {AboutComponent} from '/path';
import {ContactComponent} from '/path';

//and then either include it in a directive or mention The component Name some place in the code

But this defeats the purpose 'Dynamic Loading', as i have to know which components would be requested and also cannot lazy load them. Say i do that for 500 components, i have to load these 500 components and then just from a JSON mentioned above i pick which has to be loaded on the UI.

Solution(Completed And Tested) ==> I now do not have to mention the components i want to load in any import statement neither in any Directive. Thererby making it lazy loading and Completely Dynamic

How By Using AsyncRoute Class.

Here's my new JSON
//"route" is to map it to [routerLink]="['/Home']" & "resource" is the actual path of component

 [
  { "path" : "/about" , "name" : "About" , "component" : "AboutComponent", "route": "/About" , "resource" : "./app/about/about.component" },
  { "path" : "/contact" , "name" : "Contact" , "component" : "ContactComponent", "route": "/About" , "resource" : "./app/about/about.component" },
  { "path" : "/blog" , "name" : "Blog" , "component" : "AboutComponent", "route": "/About" , "resource" : "./app/about/about.component" },
  { "path" : "/news" , "name" : "News" , "component" : "AboutComponent", "route": "/About" , "resource" : "./app/about/about.component" }
]

Now to the code, here i fetch this JSON and add it to the routeConfigArray : RouteDefinition[] and call the Router's .config(routeConfigArray)

  let routes : any[] = data; // consist the json data in array


routes.forEach((route : any) => {
        this.routeConfigArray.push(
            new AsyncRoute({
                path : route.path,
                loader : () => System.import(route.resource).then(m => m[route.component]),
                name : route.name
            })
        );
    });

    this._router.config(this.routeConfigArray);

And thats how it works!!!

查看更多
登录 后发表回答