Check permissions before vue.js route loads - javascript

does anyone know how to check a user's permissions before a vue.js route is rendered? I came up with a partial solution by checking permissions in the created stage of a component:
created: function () {
var self = this;
checkPermissions(function (result) {
if(result === 'allowed') {
// start making AJAX requests to return data
console.log('permission allowed!');
} else {
console.log('permission denied!');
self.$router.go('/denied');
}
});
}
However, the issue is the entire page loads momentarily (albeit without any data) before the checkPermission() function gets activated and re-routes to /denied.
I also tried adding the same code in the beforeCreate() hook, but it didnt seem to have any effect.
Does anyone else have any other ideas? Note - the permissions vary from page to page.
Thanks in advance!

The thing you need is defined in Vue Router Docs as Data Fetching - https://router.vuejs.org/en/advanced/data-fetching.html
So, as docs says, sometimes you need to fetch data when route is activated.
Keep checkPermissions in created hook, and then watch the route object:
watch: {
// call again the method if the route changes
'$route': 'checkPermissions'
}
A maybe more handy solution would be move logic from created hook into separated method and then call it in hook and in watch object too, as well.(Using ES6 syntax bellow)
export default {
created() {
this.checkPermissions()
},
watch: {
'$route': 'checkPermissions'
},
methods: {
checkPermissions() {
// logic here
}
}
}

Related

Vue.js component method on creation

I'm new to Vue and I'd like to make an AJAX call every time my component is rendered.
I have a vue component lets say "test-table" and Id like to fetch the contents via an AJAX call. There are many such tables and I track the active one via an v-if/v-else-if etc.
Currently I have a cheaty solution: in the template for the component I call a computed property called getData via {{ getData }} which initiates the Ajax call but does only return an empty string. Id like to switch to the proper way but dont know how.
My code is like so: (its typescript)
Vue.component("test-table", {
props: ["request"],
data () {
return {
tableData: [] as Array<TableClass>,
}
},
template: `{{ getData() }} DO SOME STUFF WITH tableData...`,
computed: {
getData() : string {
get("./foo.php", this.request, true).then(
data => this.tableData = data.map(element => new TableClass(data))
)
return "";
}
}
}
HTML:
<test-table v-if="testcounter === 1" :request="stuff...">
<test-table v-else-if="testcounter === 2" :request="other stuff...">
...
get is an async method that just sends a GET request with request data to the server. The last parameter is only for saying the method to expect a JSON as answer. Similar to JQuerys getJSON method.
the "created" method does NOT work! It fires only one time when the component is first created. If I deactivate and activate again (with v-if) the method is not called again.
Btw: I'm using Vue 2.6.13
Lifecycle hooks won't fire every time if the component is cached, keep-alive etc
Add a console.log in each of the lifecycle hooks to see.
Change to use a watcher which handles firing getData again if request changes.
...
watch: {
request: {
handler: function() {
this.getData()
},
deep: true
}
},
created() {
this.getData()
},
methods: {
getData(): string {
// do request
}
}
#FlorianBecker try the lifecycle hook updated(). It may be a better fit for what you're trying to achieve. Docs here.
You should be able to use the mounted hook if your component is continuously rendered/unrendered using v-if, like so:
export default {
mounted() {
// do ajax call here
this.callAMethod();
},
...
}
Alternatively, you could use the created() hook but it is executed earlier in the chain, so this means the DOM template is not created yet so you cant refer to it. mounted usually is the way to go.
More info on these hooks can be found here.

Is there a way to make protected route in sapui5

getRouter: function () {
return UIComponent.getRouterFor(this);
},
onInit: function () {
firebase.auth().onAuthStateChanged((user) => {
if (!user) {
this.getRouter().navTo("login");
}
});
}
Is there a way to make my routes protected, it is no problem when the first time I type in browser, I got redirected, but if I logout from the component, and manually type in browser /admin, I am not redirected, so onInit works only one time. I am new to sapui5, and in React it is different. Can someone please help and explain if there is a way to make some kind of protection on routes. Thanks.
The correct event to listen for is patternMatched
This is an example of how to protect a single route called "myProtectedRoute":
onInit: function () {
this.getRouter().getRoute("myProtectedRoute").attachPatternMatched((event) => {
if (user.isAuthenticated) {
// load and show data
} else {
this.getRouter().navTo("login");
}
})
}
If you wish to be notified when any route is matched you can read more in this tutorial https://openui5.hana.ondemand.com/topic/4a063b8250f24d0cbf7c689821df7199

How to stop code execution after routing to error page from vuex?

I have a cart page written with VueJs and Vuex. I have an api file that acts as a wrapper around axios.
In my vuex action, I call the API and if it was successful I commit data into my mutation.
async await mounted () {
const result = this.getStatus()
if (result === "locked") {
this.$router.push({name: "LockedPage"}
}
else if (result === "expired") {
this.$router.push({name: "SessionExpiredPage"}
}
doSomething(result)
},
methods: {
function doSomething(res) {
// does something with result
}
}
The getStatus function here is from my vuex action.
const {authid, authpwd} = router.history.current.params
if (!authid || !authpwd) {
router.push({name: "SomethingWrong"})
return
}
const res = await api.getStatus
commit("SET_STATUS", res.status)
if (res.otherLogic) {
//commit or trigger other actions
}
return status
}
What's the ideal way to handle these kind of API errors? If you look you'll see that I'm routing inside the Outer component's mounted hook as well as inside the vuex action itself. Should all the routing for this status function just happen inside the vuex action?
I think how it is currently set up, when theSomethingWrong page get's routed. It'll return execution back to the mounted function still. So technically the doSomething function can still be called but I guess with undefined values. This seems kind of bad. Is there a way to stop code execution after we route the user to the error page? Would it be better to throw an error after routing the page?
Should I use Vue.config.errorHandler = function (err, vm, info) to catch these custom route erorrs I throw from the vuex action?
For general errors like expired session I would recommend handle it low at axios level, using interceptor. https://github.com/axios/axios#interceptors
Interceptors can be defined eg. in plugins. Adding Nuxt plugin as example (Vue without Nuxt will use little bit different plugin definition, but still it should be useful as inspiration) (Also window is access because snippet is from SPA application - no server side rendering)
export default ({ $axios, redirect }) => {
$axios.onError((error) => {
const code = parseInt(error.response && error.response.status)
if (code === 401) {
// don't use route path, because error is happending before transition is completed
if (!window.localStorage.getItem('auth.redirect')) {
window.localStorage.setItem('auth.redirect', window.location.pathname)
}
redirect('/login-page')
}
})
}

Vue Router - call function after route has loaded

I'm working on a project where I need to call a function AFTER the route has finished loading. However, when using the 'watch' functionality, it only loads on route change, but does so before route has finished loading. So when I attempt to run a script that targets DOM elements on the page, those elements don't exist yet. Is there any functionality in Vue Router that would allow me to wait until everything is rendered before running the script?
const app = new Vue({
el: '#app',
router,
watch: {
'$route': function (from, to) {
function SOMEFUNCTION()
}
},
data: {
some data
},
template: `
<router-view/>
`
})
You should use Vue.nextTick
In your case this would translate to:
const app = new Vue({
el: '#app',
router,
watch: {
$route() {
this.$nextTick(this.routeLoaded);
}
},
data() {
return {};
},
methods: {
routeLoaded() {
//Dom for the current route is loaded
}
},
mounted() {
/* The route will not be ready in the mounted hook if it's component is async
so we use $router.onReady to make sure it is.
it will fire right away if the router was already loaded, so catches all the cases.
Just understand that the watcher will also trigger in the case of an async component on mount
because the $route will change and the function will be called twice in this case,
it can easily be worked around with a local variable if necessary
*/
this.$router.onReady(() => this.routeLoaded());
},
template: `<router-view/>`
})
This will call the routeLoaded method every time the route changes (which I'm deducing is what you need since you are using the <router-view> element), if you also want to call it initially, I would recommend the mounted hook (like in the example) or the immediate flag on the watcher
In my opinion on this situation, you should use component life cycle method of the loaded component, either use mounted method or created method.
or if your script doesn't depend on any vue component (store) you can use router.afterEach hook
router.afterEach((to, from) => { if (to.name !== 'ROUTENAME'){ // do something }});
The solution for me was to set up a custom event in every page's mounted() hook with a mixin and listen for that event on the body for example. If you wanted to strictly tie it with the router's afterEach or the route watcher to ensure the route has indeed changed before the event was fired, you could probably set up a Promise in the afterEach and resolve it in the page's mounted() by either the event or sharing the resolve function through the window.
An example:
// Component.vue
watch: {
'$route': function (from, to) {
new Promise((resolve) => {
window.resolveRouteChange = resolve;
}).then(() => {
// route changed and page DOM mounted!
});
}
}
// PageComponent.vue
mounted() {
if(window.resolveRouteChange) {
window.resolveRouteChange();
window.resolveRouteChange = null;
}
}
In case of router-view, we can manually detect router-view.$el change after $route is changed
watch: {
'$route'(to, from) {
// Get $el that is our starting point
let start_el = this.$refs.routerview.$el
this.$nextTick(async function() { await this.wait_component_change(start_el)})
}
},
methods: {
on_router_view_component_changed: function() { }
wait_component_change: async function(start_el) {
// Just need to wait when $el is changed in async manner
for (let i = 0; i < 9; i++) {
console.log('calc_has_dragscroll ' + i)
if(start_el) {
if (!start_el.isSameNode(this.$refs.routerview.$el)) {
// $el changed - out goal completed
this.on_router_view_component_changed()
return
}
}
else {
// No start_el, just wait any other
if(this.$refs.routerview.$el) {
// $el changed - out goal completed too
this.on_router_view_component_changed()
return
}
}
await this.$nextTick()
}
},
}
You can accomplish this by hooking into VueJS lifecycle hooks:
Use VueJS Lifecycle Hooks:
Here is a summary of the major VueJS lifecycle hooks. Please consult the documentation for the full description.
i. beforeCreate: This function will be called before the component is created
ii. created: This function will be called after the component is created, but note although the component is created, it hasn't been mounted yet. So you won't be able to access the this of the component. However, this is a good place to make Network Requests that will update the data properties.
iii. mounted: This function is called once the component has been rendered and the elements can be accessed here. This is what you're looking for.
iv. beforeDestroy: This function is called before the component is destroyed. This can be useful to stop any listeners (setTimeout, setInterval..), that you created.
See the diagram below for the details.
const app = new Vue({
el: '#app',
router,
mounted(){
this.someFunction()
},
data: {
some data
},
template: `
<router-view/>
`
})
Use Vue Router Navigation Guards: Vue Router also expose some lifecycle hooks that can you hook into. However, as you will see below they do not fit your requirements:
i. beforeRouteEnter: called before the route that renders this component is confirmed. oes NOT have access to this component instance, because it has not been created yet when this guard is called!
ii. beforeRouteUpdate: called when the route that renders this component has changed, but this component is reused in the new route.
iii. beforeRouteLeave: called when the route that renders this component is about to be navigated away from.
References:
VueJS Documentation (LifeCycle): VueJS Instance
Vue Router Documentation (Navigation Guards): Navigation Guards

call a function every time a route is updated vue.js

I have integrated intercom in my app and I need to call window.Intercom('update'); every-time my url changes.
I know I could add it on mounted() but I rather not modify all my component and do it directly using the navigation guards. (Mainly to avoid to have the same code in 10 different places.
At the moment I have:
router.afterEach((to, from) => {
eventHub.$off(); // I use this for an other things, here is not important
console.log(window.location.href ) // this prints the previous url
window.Intercom('update'); // which means that this also uses the previous url
})
This runs intercom('update') before changing the url, while I need to run it after the url changes.
Is there a hook which runs just when the url has changed?
How can I do this?
Thanks
Wasn't sure this would work as what you already have seems like it should be fine but here goes...
Try watching the $route object for changes
new Vue({
// ...
watch: {
'$route': function(to, from) {
Intercom('update')
}
}
})
I just came up with another solution beyond Phil's, you could also use Global Mixin. It merges its methods or lifecycle hooks into every component.
Vue.mixin({
mounted() {
// do what you need
}
})
created() {
this.$watch(
() => this.$route.params,
() => /*Your Function*/
);
},

Categories

Resources