Storing/Retrieving Navigation Menus/Route in Database

We are trying to create a configurable hierarchical menu navigation for Aurelia using information stored in a database. Using as starting point the Microsoft Net Core SPA templates and generating the values from a controller. These options would not be known until run time as the database may be updated with new routes as needs to be dynamic. After compiling and running, the following error is logged when selecting a menu item. Are we trying to do something that is not allowed? Would there be a workaround?

“ERROR [app-router] Error: Unable to find module with ID: app/components/counter/counter”

We’ve not been able to include screenshots or a working VS 2017 project but can email one if any help.
[app.ts]
import { Aurelia, PLATFORM } from ‘aurelia-framework’;
import { Router, RouterConfiguration } from ‘aurelia-router’;
import { HttpClient } from ‘aurelia-fetch-client’;
import { inject } from ‘aurelia-framework’;

@inject(HttpClient)
export class App {
public navroutes: NavigationRoute[];
constructor(http: HttpClient) {
http.fetch(‘api/SampleData/DbRoutes’)
.then(result => result.json() as Promise<NavigationRoute[]>)
.then(data => {
this.navroutes = data;
// Add each route (including sub route(s) here
for (var i = 0; i < data.length; i++) {
var navroute: NavigationRoute = data[i];

                this.router.addRoute({
                    route: navroute.route,
                    name: navroute.name,
                    settings: { icon: navroute.icon },
                    moduleId: PLATFORM.moduleName(navroute.moduleId.toString()),      // does not work (read name of module from database) - webpack can't resolve it - workaround add in constructor   
                    //moduleId: PLATFORM.moduleName('../scheduler/scheduler'), // this works   (typed name of module) - webpack can resolve it
                    nav: navroute.nav,
                    title: navroute.title
                })
                this.router.refreshNavigation();
            }
        });

router: Router
configureRouter(config: RouterConfiguration, router: Router) {
config.title = ‘Demo’;

    config.map([{
        route: ['', 'home'],
        name: 'home',
        settings: { icon: 'home' },
        moduleId: PLATFORM.moduleName('../home/home'),
        nav: true,
        title: 'Home' // The default home
    }]);

this.router = router
}
}

class NavigationRoute {
id: number;
parentId: any;
route: string;
name: string;
icon: string;
moduleId: string;
nav: boolean;
title: string;
}



You cannot do PLATFORM.moduleName(navroute.moduleId.toString()) - this string is analysed at bundle time, not runtime. You will have to either make PLATFORM.moduleName calls for each module or use GlobalDependenciesPlugin (or AureliaPlugin with includeAll flag)

1 Like

Thanks for your help. I checked the calls work correctly at run time as long as the reference is defined useing the explicit string before hand. So needs to be recompiled every time the contents of database call change. See snippet below. Now to get sub routes working :slight_smile: Treating Routes as Modules does not seem right to me?


constructor(http: HttpClient) {
// Declare all routes returned from the database otherwise routing will not work. This needs to be recompiled whenever these change
PLATFORM.moduleName(’
/scheduler/scheduler’); // workaround
PLATFORM.moduleName(’
/newcustomer/newcustomer’); // workaround
PLATFORM.moduleName(’
/searchcustomers/searchcustomers’); // workaround
//PLATFORM.moduleName(’
/newquote/newquote’); // workaround
//PLATFORM.moduleName(’
/searchquotes/searchquotes’); // workaround
// Read the navigation from database
http.fetch(‘api/SampleData/DbRoutes’)
.then(result => result.json() as Promise<NavigationRoute[]>)
.then(data => {
this.navroutes = data;
var subroutes: NavigationRoute[] = [];
// Add each route (including sub route(s) here
for (var i = 0; i < data.length; i++) {
var navroute: NavigationRoute = data[i];
if (navroute.parentId == null) {
this.router.addRoute({
route: navroute.route,
name: navroute.name,
settings: { icon: navroute.icon },
moduleId: PLATFORM.moduleName(navroute.moduleId.toString()), // Note: this only works database routes have been explicitly compiled previously
nav: navroute.nav,
title: navroute.title
})
this.router.refreshNavigation();
}
}
});
}

The route itself is not a module, the module gets rendered as a navigation result.

It’s not really a workaround, but rather a documented way in webpack. The subroutes are defined in respective modules via configureRouter function.

If you need truly dynamic routing you’ll have to use cli with require or jspm - they are capable of loading modules outside bundles

I’m having trouble getting these plugins to work: AureliaPlugin will work with includeAll: false but not includeAll: true. I can’t get the GlobalDependenciesPlugin to work at all. Could you point me in the right direction to configure an example so I can put a few PLATFORM.moduleName s in a file to get this working? The following webpack.confiog.js just blows up


plugins: [
new webpack.DefinePlugin({ IS_DEV_BUILD: JSON.stringify(isDevBuild) }),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(’./wwwroot/dist/vendor-manifest.json’)
}),
new GlobalDependenciesPlugin(),
new AureliaPlugin({ aureliaApp: “boot”, includeAll: false })
].concat(isDevBuild ? [
new webpack.SourceMapDevToolPlugin({
filename: ‘[file].map’, // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(bundleOutputDir, ‘[resourcePath]’) // Point sourcemap entries to the original file locations on disk
})
] : [
new webpack.optimize.UglifyJsPlugin()
])
}];

It’s not really a flag, you need to set it to a path, relative to config location

I’ve used the following configuration but keep getting the TypeScript error message ‘Unable to find module with ID: boot’. It loads but does not compile all the files if includeAll is set to false

new AureliaPlugin({ aureliaApp: ‘boot’, includeAll: ‘
/
/app/components/app’ })

First of all, that relative path doesn’t look right, should be something like “src/components”


Secondly, is there “boot” file?

Have a look at this example for webpack 4

This is Net Core project produced using Microsoft SPA templates (https://blogs.msdn.microsoft.com/webdev/2017/02/14/building-single-page-applications-on-asp-net-core-with-javascriptservices/) and there is no ‘src’ directory present. There is a boot.ts file in ClientApp directory hence the directory should be resolvable (ClientApp/app/components/app contains the app.html and app.ts files). The application will only load at all if includeAll is either set to false or not present. It looks like there may be more hardcoding than expected in the JavaScript/TypeScript so like looking for a needle in a haystack. (VS 2017 project has 12,530 files!)

Then includeAll: "ClientApp/app/components"

Afraid still getting the same error. There could be versioning problems in the packages due to Node and NPM versions since expected behaviour is not being observed here. In desperation I have created a routes.ts file in the app folder and added and import ‘./routes’; which does work (the constructor runs but cannot be stepped through) without the plugin needing configuration. This routes.ts file contains the following and Webpack compiles the references.

import { Aurelia, PLATFORM } from ‘aurelia-framework’;

export class DbRoutes {
constructor(http: HttpClient) {
PLATFORM.moduleName(’
/scheduler/scheduler’);
PLATFORM.moduleName(’
/newcustomer/newcustomer’);
PLATFORM.moduleName(’
/searchcustomers/searchcustomers’);
//PLATFORM.moduleName(’
/newquote/newquote’);
//PLATFORM.moduleName(’
/searchquotes/searchquotes’);
}

Would it be helpful if I sent you a zipped up project file since the Microsoft SPA template may be causing issues (or something else)

How about a fresh dotnet project on GitHub to which I can suggest a pr?

Sounds good plan! Any particular repository?

Nope, you can create a public one and post a link here.

I have now implemented a solution to this problem (simply adds a Scheduler navigation and page) at the repository https://github.com/parkingtonl/pioneer.

If the database is not set up or configured correctly a dialog is displayed after the promises have completed

I’ve created a PR. Webpack builds and the modules are in the bundle.
Mind you, the project is using old packages throughout - e.g. webpack 2.
Also, splitting is not optimal - two config files are needed. It can be made simpler.
If I get some time tomorrow, I’ll create a repo with the updated dotnet template.

Just wondering if there is an easy way to do hierarchical (nested) navigation menus? Not many working examples available. I’ve tried applying a subRoutes as property but only nests one level and seems uncooperative?

It has been noticed the builds for some NPM dependencies are very easily broken, for example, importing an NPM such as sweetalert using npm install sometimes remove up to a hundred packages and breaks the build. The only way to get it working again is to have a working copy of the node_modules backed up at all times as webpack appears to bust itself. This is being developed for a commercial project so needs to be made much more robust when time permits.

AFAIK, there is no subRoutes field. I remember seeing an enhancement for the router to be able to get all the routes with children. Not sure it’s been implemented, may be @Alexander-Taran knows.

In the mean time, I register all the routes in the root view and provide an additional parameter - root menu name. Then your nav element can group routes by this parameter and display them in suitable fashion.