问题
I want to load menu options dynamically. so I'm wondering the best approach
I am able to use the code below to add routes after the page is loaded. This works for normal navigation, but does not work during a refresh.
Can configure router return a promise / how do I load menu items into the route?
@inject(HttpClient)
export class DocumentMenu {
router: Router;
documents : IDocument[];
heading = 'Document Router';
constructor(public http: HttpClient) {}
activate(): void {
this.http.fetch('http://localhost:17853/Document/GetDocuments?folderID=13244')
.then<IDocument[]>(response => response.json())
.then<IDocument[]>(docs => {
if ( docs ){
for( var doc of docs){
this.router.addRoute( { route : doc.DocumentID.toString(), name : doc.Name, moduleId: './documents/document', nav:true, title: doc.Name });
}
this.router.refreshNavigation();
}
return docs;
});
}
configureRouter(config: RouterConfiguration, router: Router) {
var routes = new Array();
routes.push(
{ route: 'index', name: 'index-name', moduleId: './documents/index', nav: false, title: 'Documents' } );
routes.push( { route: '', redirect: 'index' } );
config.map( routes );
this.router = router;
}
}
回答1:
This does not answer your question, but I think it may be helpful to you and others with a similar issue.
The Dynamic Route Anti-Pattern
Your application has a number of different routes, all of which vary based on the state of the application. Therefore, you must first fetch the data, and then build the routes, and then register them with the router.
The reason this is an anti-pattern is because you will continuously need to update the router based on the state of the application, when Aurelia itself is built with static ways of describing dynamic content.
Dynamically Routing Homogeneous Data
Let's say you are building Google Drive, and you have a number of various files that could change as the user adds and removes them. For this case you have two categories of routes: Folders and Documents. Therefore, you make one route for each.
configureRouter(config) {
config.map([
{ route: 'folder/:id', moduleId: 'folder' }
{ route: 'document/:id', moduleId: 'document' }
}
}
class FolderViewModel {
activate({ id }) {
// get your specific folder data and load it into your folder view model
this.fetch('getDocuments?folderId=${id}')
}
}
class DocumentViewModel {
activate({ id }) {
// get your specific document and load it into your document view model
this.fetch('getDocuments?documentId=${id}')
}
}
Dynamically Routing Hetergeneous Data
Let's say instead you want to build YouTube. When user mjd10d logs in, he is welcome to watch videos to his heart's content, but he is not a premium content creator, and doesn't have access to the content creation portion of the site. The best way to handle this is to leave all possible routes in your application, and filter them based on the user's credentials in an AuthorizeStep
.
configureRouter(config, router) {
config.addPipelineStep('authorize', AuthorizeStep);
}
@inject(UserSession)
class AuthorizeStep {
constructor(UserSession) {
this.user = UserSession;
}
run(navigationInstruction, next) {
var instructions = navigationInstruction.getAllInstructions()
if (!this.authorized(instructions.config)) {
return Redirect('404');
}
return next();
}
authorized(routeConfig) {
// something smart that returns false if unauthorized
return this.user.permissionLevel > routeConfig.requiredPermission;
}
}
Though not all cases will be authorization related, you can always register your own pipeline step using the addPipelineStep API
回答2:
You can add routes dynamically (at startup or anytime for that matter) by having a single fixed (static) route in the "configureRouter" method (in app.ts), to which you then add all the other routes dynamically, when your fetch completes, like so:
configureRouter(config, router) {
config.title = 'SM';
//configure one static route:
config.map([
{ route: ['', 'welcome'], name: 'welcome', moduleId: 'welcome/welcome', title: 'Welcome' }
]);
routeMaps(this.navRepo) //your repo/service doing the async HTTP fetch, returning a Promise<Array<any>> (i.e., the routes)
.then(r => {
r.forEach(route => this.router.addRoute(route));
//once all dynamic routes are added, refresh navigation:
this.router.refreshNavigation();
});
this.router = router;
}
The "routeMaps" function is just a wrapper around the repo call and a mapping of the result to the Array of route items.
回答3:
You can return a promise in activate. if activate() returns a promise, configureRouter() doesnt fire until the promise returned in activate() is resolved.
I ended up preparing the routes in activate like below:
activate(){
return this.http.fetch('url')
.then(response => response.json())
.then(docs => {
this.routerMapped = docs;
});
}
configureRouter(config, router) {
//build the routes from this.routermapped if necessary
config.map( this.routerMapped );
this.router = router;
}
回答4:
To make this work, I created the routes in the constructor with a synchronous request
export class DocumentMenu {
...
routes : RouteConfig[];
constructor(http: HttpClient) {
this.http = http;
var folderID = window.location.hash.split('/')[2]
this.routes = new Array<RouteConfig>();
this.routes.push ( { route: 'index', name: 'index-name', moduleId: './documents/index', nav: false, title: 'Documents' });
this.routes.push ( { route: '', redirect: 'index' } );
for( var route of this.getRoutes( folderID )){
this.routes.push( route );
}
}
getRoutes(folderID: string) : RouteConfig[]
{
var routes = new Array<RouteConfig>();
var docsURL = 'http://localhost:17853/Document/GetDocuments?folderID=' + folderID;
// synchronous request
var docsResp = $.ajax({
type: "GET",
url: docsURL,
async: false,
cache:false
}).responseText;
var docs = JSON.parse( docsResp );
for( var doc of docs ){
routes.push( { route : doc.DocumentID.toString(), name : doc.Name, moduleId: './documents/document', nav:true, title: doc.Name });
}
return routes;
}
configureRouter(config: RouterConfiguration, router: Router) {
config.map( this.routes );
this.router = router;
}
...
来源:https://stackoverflow.com/questions/37647971/aurelia-load-routes-dynamically-from-fetch