Best practice to dynamically choose view model based on dynamic variable using the same Aurelia route? - aurelia

I know that Aurelia route modules can be specified dynamically using navigationStrategy but that does not work for my purposes because the toggle value resides in the RouterConfiguration that only runs once. I need a way to route to different views where the toggle value can change multiple times during one session. Additionally, not all routes have multiple views.
What is the best practice for routing to different views on the same route based on a dynamic value?
I've come up with a few different strategies but I'm not sure which one is the most acceptable way to do this.
Using viewPorts where the route will have a static moduleId to a view that injects the name into an instance of <router-view name="view1_index"></router-view> using a global string, e.g. 'view1' may be passed down as 'view2', etc.
Using 2 or more instances of <compose> where the route will again have a static moduleId to a view that will use a global variable to toggle an if.bind in the <compose> instances
Using canActivate in the route module and redirecting to the secondary viewport if conditions are met
Using a pipeline step in the router config to evaluate whether it should direct to a different module (if this is possible)
Which of these strategies, if any, is most accepted? If all of these are odd ways of routing to different views per route, what should be done?

What is the best practice for routing to different views on the same
route based on a dynamic value?
Compose is your best bet. It is common to pass a parameter to the route, capture the parameter in the activate(params) callback, set a variable on your view model using the params, and then use that variable to set the view-model attribute of <compose>.
Using a pipeline step in the router config to evaluate whether it
should direct to a different module (if this is possible)
This is very possible. A common use case is authentication, where you use the AuthorizeStep to check whether a user is authorized and redirect him away if he is not. See http://foreverframe.net/exploring-aurelia-pipelines/. This can be activated in the PreactivateStep as well.
Using viewPorts where the route will have a static moduleId to a view
that injects the name into an instance of using a global string, e.g. 'view1'
may be passed down as 'view2', etc.
I recommend against using viewports for anything other than associating routes to views on a certain section of the screen.
Edit:
A third option you might be interested in is returning a Redirect from your canActivate() function.
import { Redirect } from 'aurelia-router';
canActivate(params) {
let thing = this.thing = await this.service.getById(params.id);
if (!thing) {
return new Redirect('somewhere');
}
return true;
}

Related

Nuxt choosing layout before initialize

I use nuxt-generate and nuxt/device package. which provides the device type but for the layout since I use nuxt-generate at first load app uses default layout. not the one provided by nuxt/device. but after i go another page it starts using correct layout. so the problem is how can i have the app use layout at first initialize. or at least change it after initialize. is there any way to do these?
this is how i try to choose layout but never works at first open.
layout (context) {
if(context.isMobile) {
return 'mobile'
} else if (context.isDesktop) {
return 'default'
}
},
when you use nuxt-generate nuxt generates a series of static html files, I don't think there is an easy way to solve this, but there can be a series of workarounds:
use nuxt in ssr mode, so that you have a way of knowing who is calling that particular resource
make a distinction at the level of routes like (/desktop/*** and /mobile/***) where both routes lead to a different layout and in the webserver configuration do a redirect on the correct route
do a middleware that redirects to the correct route /desktop /mobile, but I don't recommend it because you lose the advantage of static generation of nuxt

path-to-regexp Find regular expression matching the route

I am adding dynamic child components while the page loads. Adding child routes in OnCreated of parent does not make the page work when we refresh the page.
Hence, I am parsing the page templates (as I know them when the page loads).
I am now looking for a way to know the route which matches the href. Vue-js uses path-to-regexp and my question is very simple.
I want to know the matching component
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User },
{ path: '/foo/bar', component: FooBar },
]
})
// Reverse of this
var matchingComponent = howDoIDothis(/foo/bar) // this should give me the matching
I need this so that I can remove from the path and then add the child component to the parent dynamically.
You need to use Vue router's router.getMatchedComponents method. However, this method needs that your router is fully initialized with all the routes. Otherwise, there is no way. Vue-router doesn't expose underlying parsed Regular expressions against which you can compare your href.
Also, what you are trying to do is not the most idiomatic way of doing things in Single Page Applications. A good practice is to declare all your routes upfront in some JS file using which you should initialize your router. Of course, you will want to protect certain routes for which you should use Route guards.
Finally, when you have all your routes declared upfront means you have all the components bundled upfront in one big JS file. To avoid this, wrap your component in async wrappers and bundler like Webpack would be smart enough to split the bundle into multiple smaller files.

Vuejs have multiple components sharing variables and functions

Right this is more about practice, I haven't really got a working scenario but I can provide an example. Imagine Facebook messenger, you have a chat widget in the header where you can quickly view and respond to messages. You can also click view all to take you off to the messages page.
So to me I can see myself having two Vue components, they're more sibling than parent and child as one would be used on all pages and the other simply on the messages page.
<chat></chat> and <chat-widget></chat-widget>
Now from what I can see, across the widget and the chat window itself is functions that would operate in the same way, maybe they'll have slightly different templates because of where they are loaded but somethings straight of the bat would be:
messages() {}
compose() {}
validate() {}
These are just some examples as I use Laravel as my backend I would be using axios to send requests between my Vue frontend and my backend (database).
Ideally, I wouldn't want these components to duplicate it's functions, so should I simply duplicate them or is there a way where i can store some sort of parent functions?
One problem is because of async ajax requests I can't simply call a function that returns say the messages for me to bind, at least, I don't think I can.
Just looking for some guidance on how I can best do this with Vue so that I'm not duplicating identical functionality within my components.
You can use composition to create a "base/abstract" component from which other components can extend:
https://v2.vuejs.org/v2/api/#extends
var CompA = { ... }
// extend CompA without having to call `Vue.extend` on either
var CompB = {
extends: CompA,
...
}
You can inherit the functionality from the "base/abstract" class or override parts of the functionality of the parent.
Another thing you could do is to create mixins:
https://v2.vuejs.org/v2/guide/mixins.html#ad
This is helpful if your components need to share functionality but are not largely the same. If you were to use extend in those cases you would likely override the majority of base functionality to these are a better in that case.
Of course nothing would stop you from simply extracting commonly used methods into a separate file and importing them in your components directly.

Root VM's activate() won't receive query params

In Aurelia, the root view-model's (app.ts, containing router config) activate(params) method doesn't receive query string parameters.
For the address: http://localhost:5000/home?param=1, the Home view-model (home.ts) is able to read query string param via activate() method, but activate() params in App view-model (app.ts) are empty.
I have router configured to use pushstate, but the behavior is the same with disabled pushshate.
Is there a way how to read query string params inside app root view-model?
It's not going to get parameters passed to it b/c it isn't activated by the router. That being said, you can simply use VanillaJS to do this. There is a new API, that is polyfillable that you could use: https://davidwalsh.name/query-string-javascript
Remember, Aurelia is just JavaScript. This is something I say in every intro to Aurelia talk I give around the world, and it's important to take this to heart.

Attach/Render RactiveJS component outside of template

I've got an existing SPA that was developed using nested RactiveJS components. Which is great, and offers a ton of flexibility throughout the entire app. Currently I attempting to add in client side routing support using page. My navigation switches out high-level components using simple {{#visible}}{{/visible}} template markup on each component. This is a little troublesome in its current state as it always kicks off a re-render whenever the high-level component becomes visible again.
Is there a way to render a component, for example, called widget, without using the
<widget></widget>
method? I've already "registered" the component with the parent, but obviously when constructing it by means of
new App.components.widget
I am able to control how/when it's rendered/inserted/detached, but lose the recognition in the application's component hierarchy.
There is insert exactly for that. You don't even need to "register" it to the component you plan to put it to. You can use the different find* methods or nodes to easily retrieve a reference of your planned container element.
var instance = new YourDetachedWidget({ ... });
instance.insert('#your-container'); // This could be a node, selector or jQuery object