How to access url parameters when configuring child router - aurelia

I am trying to generate some routes dynamically in a child router basing on a parameter provided within the url.
So I have my main router configured with that route :
mydomain.com/#/page/:page that is loading a module "page"
And in my module page I have a configureRouter function which is supposed to fetch the sections relative to the page specified in the url before to add them in the child router:
public async configureRouter(config, router){
page = ???
sections = wait fetchPageSections(page)
//for each section, add route in child router
}
My problem here is how to retrieve the :page parameter, since this will be available only (if I understand well) in the activate() function, that will be called after configureRouter(). However, since that part of the route has already been "matched" in the parent router, I think there should be a way to retrieve it in the configureRouter function.
Thanks.

I don't know if there is a way to retrieve the :page parameter in the configureRouter() method, because as far as I know, at this point the new route has not been triggered yet.
You can retrieve the :page parameter in the created() method.
created() {
let page = this.router.parentInstruction.params.page;
// do something and add routes
//this.router.addRoute({ route: "test", moduleId: "test", nav: true, title: "test" });
//this.router.refreshNavigation();
}
A second option would be using a global object to hold the desired parameter. You could inject this object in the constructor() method of the view component. However, this would be an overkill and unsafe to use. I do not think it is a good idea.
A third option, and the easiest one in my opinion, is using mapUnknownRoutes() for dynamic routes:
router.configure(config => {
config.mapUnknownRoutes(instruction => {
//read the instruction.fragment to get
//you can return a promise and make async requests
return instruction;
});
});
I hope this helps!

The router you are passed in configureRouter(config, router) is the child router. Luckily, it has a parent property that will provide you with what you want.
router.parent.currentInstruction.params.page should give you what you're looking for: the page parameter from the parent router.

Related

Vue2 $router.replace() does not call mounted hook

I have Nuxt Vue 2 app. There is a redirect in the mounted hook to the same route. The only difference in this route is query string. It looks like
mounted() {
...
if( !isTokenOwner ) {
const result = await this.$api.campaignNewShare.copyNewShare(this.newShareToken);
localStorage.setItem(result.data.token, new Date().getTime());
this.$router.replace({'name': 'campaigns-new', 'query': {token: result.data.token}});
this.loading = false;
return;
}
}
It seems that Vue stays on the same page and only replace the url query string parameter. But I need to redirect to the new location with whole new lifecycle.
Can somebody tell me what really happened there after the replace() call? Why it does not trigger the real redirect? Thnaks.
Ok so as documentation says
One thing to note when using routes with params is that when the user
navigates from /user/foo to /user/bar, the same component instance
will be reused. Since both routes render the same component, this is
more efficient than destroying the old instance and then creating a
new one. However, this also means that the lifecycle hooks of the
component will not be called.

Nuxt pass props programmatically, through router

i'm using Nuxt
I'm having troubles with passing data from one page to another
I would like programmatically to navigate to other page, and pass some data to other page (in this case its javascript object)
So here is my code so far:
I have a component in which I navigate from:
this.$router.push({ path: 'page/add', props: { basket: 'pie' } });
And here is a component where I would like to get data, its a Nuxt page:
export default {
components: { MyComponent },
props: [
'basket' // this is also empty
],
async asyncData(data) {
console.log(data); // data does not contain basket prop
},
meta: {
breadcrumb: {
path: '/page/add',
},
},
};
</script>
But when I try to acces props, or data or data.router it does not contain basket prop ??
Also, I would not like to use query, or params because they change URL
[1]: https://nuxtjs.org/
You can use localstorage and save you'r data in it:
localStorage.setItem("nameOfItem", Value);
and delete it if you want after you'r done with it:
localStorage.removeItem("nameOfItem");
If you don't want to use query or params, I would check out the vuex store. Its a really cool way of storing global variables and use it in multiple pages.
Vuex store
Navigate to a different location
To navigate to a different URL, use router.push. This method pushes a new entry into the history stack, so when the user clicks the browser back button they will be taken to the previous URL.
The argument can be a string path, or a location descriptor object. Examples:
// literal string path
this.$router.push('/users/eduardo')
// object with path
this.$router.push({ path: '/users/eduardo' })
// named route with params to let the router build the url
this.$router.push({ name: 'user', params: { username: 'eduardo' } })
// with query, resulting in /register?plan=private
this.$router.push({ path: '/register', query: { plan: 'private' } })
// with hash, resulting in /about#team
this.$router.push({ path: '/about', hash: '#team' })
reference:
https://router.vuejs.org/guide/essentials/navigation.html#navigate-to-a-different-location
To navigate to a different URL, use router.push. This method pushes a new entry into the history stack, so when the user clicks the browser back button they will be taken to the previous URL.
What you are trying to accomplish is not conform with the browser (history etc.) or
http protocol (GET/POST).
Also, when using path params and other variables, such will be ignored, as per the documentation.
Note: params are ignored if a path is provided, which is not the case for query, as shown in the example above. Instead, you need to provide the name of the route or manually specify the whole path with any parameter.
Using props here is very likely the wrong approach, as you will never get that data to the component.

Dynamically add child routes in Vuejs

According to this official example, we have the ability to add nested/children routes in vuejs. But I cannot find any help/docs around a way to add these children routes dynamically. e.g only add child routes when Parent route is visited.
Currently all the routes in a Vue application are defined in a single place where we create Router instance. There is an api called addRoutes, but I don't know how to use that to add lazily loaded features of application along side their routes. If someone is familiar with Angular2+ Module system, that has this ability to define routes for the feature modules inside that module and even make them lazily loaded. Wondering if something could be achieved with VueJs?
You can use $router.addRoutes to re-add a route, specifying children.
You'll need to get the current route definition (as opposed to the $route object) by searching the $router.options.routes array for the route definition object that matches the current $route.path. Then add a children array of route definitions to the object and pass it to $router.addRoutes.
created() {
let { routes } = this.$router.options;
let routeData = routes.find(r => r.path === this.$route.path);
routeData.children = [
{ path: 'bar', component: Bar },
{ path: 'baz', component: Baz },
];
this.$router.addRoutes([routeData])
}
Here's an example fiddle of dynamically adding child routes in the created hook of a route's component definition.

Durandal: Conditional Start Module When Activating the Router

My Durandal application's startup logic in shell.js requires sending the user to one of two possible views depending on some conditional logic. Basically, if certain options have not previously been selected I'll send them to a Setup page, otherwise I'll send them to the normal start page.
Previously, in Durandal 1.x I would just pass a string indicating the starting module when calling activate. But in Durandal 2.x that has been deprecated.
So first, I'm wondering what is the recommended way to do this from the standpoint of the routes array? Should I just register both routes as if neither is the start module (like below) then conditionally add a another route to the array with the default route of ''?
{ route: "setup", moduleId: "setup", title: "Setup", nav: false },
{ route: "Students", moduleId: "students", title: "Students", nav: false }
The second part of my question involves how to handle the need to make a call to a web service as part of my conditional logic for determining which module is the start module. My startup logic involves checking the local storage of the browser, but sometimes I'll also need to make an ajax request to the server to get a bit of information.
My understanding is that router.activate() is a promise. Can I actually just create my own promise and resolve it by calling router.activate() after the ajax call has completed? Or is there another way I'll need to handle that? Sample code for how I was thinking I might handle this:
var deferred = $.Deferred();
//Setup and conditional logic here
var routes = setupRoutes();
$.get('/api/myStartupEndpoint')
.done(function(results) {
//check results and maybe alter routes
deferred.resolve(router.map(routes).activate());
});
return deferred.promise();
Does that make sense? I'm still converting my app over to Durandal 2.0.1 so I haven't been able to try this yet, but regardless of whether it does or not I want to find out what the recommended approach would be in this scenario.
The way I'd do it is this - actually I think it's similar to how you're already thinking, so I hope it makes sense:
In your main.js, set your application root to the same module, no matter the circumstances. Since one of the main features of D2 is child routers, I'd suggest using the module name "root" for your application root, as it makes it easier to distinguish from "shell" modules (which I use for setting up child routers):
app.start().then(function () {
app.setRoot("root", "entrance");
});
In your root module, setup the routes as you have described:
{ route: "setup", moduleId: "setup", title: "Setup", nav: false },
{ route: "students", moduleId: "students", title: "Students", nav: false }
When your root module activates, check if the user has been setup or not. Use the result of that check to work out if you want the user to be sent to the setup page, or the students page. Note however that you must activate your router before redirecting; you can use $.when to help you here:
var checkUserSetup = function () {
var deferred = $.Deferred();
// check local storage, fire off ajax request, etc.
// resolve the deferred once you know if the user is setup or not
deferred.resolve(false);
return deferred.promise();
};
root.activate = function() {
mapRoutes();
var deferred = $.Deferred();
$.when(checkUserSetup(), router.activate()).done(function(isUserSetup) {
if (isUserSetup) {
router.navigate("students");
} else {
router.navigate("setup");
}
deferred.resolve();
});
return deferred.promise();
}
Hopefully this also answers the second part of your question; but just in case - yes, you can return a promise from the activate method, and durandal will "block" until you've resolved that promise. However, note that the router's activate method also returns a promise - so your code won't quite work. You're resolving your deferred with another deferred; you'd need to do something more like this:
root.activate = function() {
var deferred = $.Deferred();
//Setup and conditional logic here
var routes = setupRoutes();
$.get('/api/myStartupEndpoint')
.done(function(results) {
//check results and maybe alter routes
//don't resolve the deferred until the router has completed activation
router.map(routes).activate().done(deferred.resolve);
});
return deferred.promise();
}
Hope that helps.

How to defer routes definition in Angular.js?

I have configured some basic routes that are available for all users before they log in:
App.config(function ($routeProvider) {
$routeProvider.
when('/login', { templateUrl: 'views/login.html', controller: PageStartCtrl.Controller }).
otherwise({ redirectTo: '/login' });
});
So the only thing user can do is to log in. After the user logs in, I would like to register additional routes like this:
$http
.post('api/Users/Login', { User: userName, Password: userPassword })
.success(function (response : any) {
App.config(function ($routeProvider) {
$routeProvider
.when('/dashboard',
{ templateUrl: 'part/dashboard.html',
controller: DashboardCtrl.Controller });
});
However, I suppose I should call .config method only once, because the $routeProvider is brand new instance that knows nothing about /login route. Further debugging showed me that the first instance of $resourceProvider is used when resolving view change.
Q: Is there a way how to register routes later?
Solution from Add routes and templates dynamically to $routeProvider might work, but is quite ugly (involved global variable nastyGlobalReferenceToRouteProvider).
Since routes are defined on a provider level, normally new routes can only be defined in the configuration block. The trouble is that in the configuration block all the vital services are still undefined (most notably $http). So, on the surface it looks like w can't define routes dynamically.
Now, it turns out that in practice it is quite easy to add / remove routes at any point of the application life-cycle! Looking at the $route source code we can see that all the routes definition are simply kept in the $route.routes hash which can be modified at any point in time like so (simplified example):
myApp.controller('MyCtrl', function($scope, $route) {
$scope.defineRoute = function() {
$route.routes['/dynamic'] = {templateUrl: 'dynamic.tpl.html'};
};
});
Here is the jsFiddle that demonstrates this in action: http://jsfiddle.net/4zwdf/6/
In reality, if we want to be close to what AngularJS is doing the route definition logic should be a bit more complex as AngularJS is also defining a redirect route to correctly handle routes with / at the end (make it effectively optional).
So, while the above technique will work, we need to note the following:
This technique depends on the internal implementation and might break if the AngularJS team decides to change the way routes are defined / matched.
It is also possible to define the otherwise route using the $route.routes as the default route is stored in the same hash under the null key
I found that the answer by #pkozlowski.opensource works only in angularjs 1.0.1. However, after angular-route.js becomes an independent file in the later version, directly set the $route doesn't work.
After reviewing the code, I find the key of $route.routes is no longer used to match location but $route.route[key].RegExp is used instead. After I copy the origin when and pathRegExp function, route works. See jsfiddle:
http://jsfiddle.net/5FUQa/1/
function addRoute(path, route) {
//slightly modified 'when' function in angular-route.js
}
addRoute('/dynamic', {
templateUrl: 'dynamic.tpl.html'
});