Vue - Accessing the Vue instance beforeEnter to access function - javascript

I am having an issue accessing the 'Vue' in a beforeEnter function.
When a session has expired I have a small toast show that tells the user to login again.
When toast contains a button, which when clicked I'd like to trigger another modal to allow the user to login. This is contained in the 'Vue' as a sperte component.
How do I access the 'Vue' ('this') to trigger the modal?
I've tried; this.app and this.a.app - among others lists on SO and elsewhere, to to avail.
Thanks.
Route
{
path: "/dashboard",
name: "dashboard",
component: Dashboard,
beforeEnter: protectedPage,
meta: {
title: "Dashboard"
}
},
Function
function protectedPage(to, from, next) {
if (VueJwtDecode.decode(localStorage.token).exp < Math.floor(Date.now() / 1000)) {
localStorage.removeItem("token");
Vue.toasted.show("The session has ended. Please login.", {
theme: "toasted-primary",
position: "top-center",
duration: null,
action: {
text: "Login",
onClick: (e, toastObject) => {
// CODE HERE TO TRIGGER LOGIN MODAL
next("/");
toastObject.goAway(0);
}
}
});
Vue.toasted.hide();
next("/");
}
}

I'm not sure you can on a beforeEnter looking at the docs but you can achieve the same on a beforeRouteEnter inside the component itself. By passing a callback.
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},

Related

Page with the authRequired meta still gets shown for a split second even when no one is logged in on Firebase

I'm new to Vue / web development, ever since I started i've had a good amount of fun but now i'm stuck.
Currently I am creating an admin dashboard with Firebase authentication. Everything seems to work like it should but there is one thing that I still don't understand.
In the Vue Router I have all the different routes set up and the dashboard is inaccessible (if not logged in to Firebase).
This is my Router Guard:
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/register",
name: "Register",
component: Register,
},
{
path: "/login",
name: "Login",
component: Login,
},
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
meta: {
requiresAuth: true,
},
},
];
router.beforeEach((to) => {
//If route requires authentication
if (to.matched.some((rec) => rec.meta.requiresAuth)) {
//Check firebase user
auth.onAuthStateChanged((user) => {
console.log(user);
//If the user object does not exist then redirect to the /Login page
if (!user) {
router.push("/login");
}
});
}
});
If I now try to open localhost:8080/dashboard it shows the dashboard for a split second and then hops to the /login page.
If also tried it with next({name: 'Login'}) but for some reason I keep getting white pages when I use next().
Hope someone can help me.
Thanks :)
The problem is that instead of checking for a logged-in user you are adding an "event listener" auth.onAuthStateChanged.. it fires async when the authentication state changes. However you need a sync check here. The best would be to use vuex and store your user in a global state. Though if you don't use vuex at all and you need your user only here, then probably a "local" user var in the router would also work.. something like this:
let user = null
auth.onAuthStateChanged(u => {
console.log(u)
if (u) {
user = u
} else {
user = null
}
})
router.beforeEach(to => {
if (to.matched.some(rec => rec.meta.requiresAuth)) {
//Check firebase user
if (!user) {
router.push("/login");
}
}
})
It's just a concept, but it should work.. let me know :)

Vue-router - How to cancel operations when user clicked menu links

Not specific to Vue.js but to Javascript Single Page applications. If you have a form and a rather long running submit action, like saving something. The submit operation should save something and then pushing to a new route for a success message.
While waiting for the result, the user clicks on a different link and is going away.
See this fiddle:
https://jsfiddle.net/hajbgt28/4/
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
methods: {
async submit() {
await setTimeout(() => {
this.$router.push("/bar");
}, 5000);
}
}
};
const Foo = { template: '<div>Foo</div>' }
const Bar = { template: '<div>Bar</div>' }
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '/', component: Home },
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
})
new Vue({
router,
el: '#app',
data: {
msg: 'Hello World'
}
})
Click Home
Click the button
Click on "Foo" immediately, you see "Foo"
Wait a few seconds
The Page changes to "Bar"
I have a two solutions in my mind:
I can check inside the submit operation if I am still on the route I expect and only proceed if the user is still on this page. It is rather complicated to check it every time
Disable all links on the page while loading. But this makes the page useless until the operation is finished.
What is the best practice for situations like this?
You could use a beforeRouteLeave navigation guard to abort that action (i.e., cancel the timer in your example) upon switching routes.
Assuming identifiable submit actions, save the ID of the operation result (i.e., save the timer ID from setTimeout's return value in your example).
Add a beforeRouteLeave handler to the component to cancel the submit action (i.e., clear the timer ID in your example).
const Home = {
methods: {
submit() {
this.timerId /* 1 */ = setTimeout(() => {
this.$router.push("/bar");
}, 5000);
}
},
beforeRouteLeave (to, from, next) {
clearTimeout(this.timerId) /* 2 */
next()
}
};
updated jsfiddle
Here's one idea: make a component that provides (using Vue's provide/inject API):
A function that starts an operation. This is called when a form is sent. It provides a whenDone callback which is either executed or ignored, depending on if the operation is cancelled.
A function that cancels all pending operations. The cancel function could be called when the user navigates away.
The implementation could look like this:
const CancellableOperationProvider = {
name: "CancellableOperationProvider",
props: {},
data: () => ({
pendingOperations: []
}),
/*
* Here we provide the theme and colorMode we received
* from the props
*/
provide() {
return {
$addOperation(func) {
this.pendingOperations.push(func);
func(function whenDone(callback) {
if (this.pendingOperations.includes(func)) callback();
});
},
$cancelAllOperations() {
this.pendingOperations = [];
}
};
},
render() {
return this.$slots.default[0];
}
};
The usage would look like this:
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
inject: ['$addOperation', '$cancelAllOperations'],
methods: {
async submit() {
this.$addOperation(whenDone => {
await setTimeout(() => {
whenDone(() => this.$router.push("/bar"));
}, 5000);
});
}
}
};
You could then add a navigation guard to the Vue Router so that $cancelAllOperations is called after clicking any link. Since $cancelAllOperations is only accessible through the inject API you will have to make a component that imperatively adds a navigation guard to the Vue router after mounting and removes it when unmounting.
Let me know if it doesn't work--I haven't done Vue in a while.
I used the answer from tony19 to make solution which fits my needs for use cases without setTimeout too:
const Home = {
template: '<div><button #click="submit">Save and go Bar!</button></div>',
data() {
return {
onThisPage: true
}
},
beforeRouteLeave(to, from, next) {
this.onThisPage = false;
next();
},
methods: {
submit() {
setTimeout(() => {
if (this.onThisPage) {
this.$router.push("/bar");
}
}, 5000);
}
}
};
See here: https://jsfiddle.net/ovmse1jg/

Advanced Vue router guards

So, I have my routers set up like this:
const routes = [
{
path: '/',
name: 'HomePage',
component: HomePage,
beforeEnter: checkAuth,
meta: {
requiresAuth: false,
showSidebar: false
}
},
{
path: '/feed',
name: 'FeedPage',
component: FeedPage,
beforeEnter: checkAuth,
beforeRouteEnter: ((to, from, next) => {
console.log(from)
}),
meta: {
requiresAuth: true,
showSidebar: true
}
},
{
path: '/faq',
name: 'FAQPage',
component: FAQPage,
beforeEnter: checkAuth,
meta: {
requiresAuth: true,
showSidebar: true
}
},
}
]
So checkAuth is a function that basically checks wether the user is authenticated before Entering that route (using google auth), which works perfectly. But I also want to use beforeRouteEnter to check whether the user's designation is allowed in that route AFTER authentication. I have the designation stored in the Vuex store.
How can I use this keyword such that I can use the plugins?
And also, what is the proper way to use beforeRouteEnter?
Based on the Vue Router documentations, The beforeRouteEnter guard does NOT have access to this, because the guard is called before the navigation is confirmed, thus the new entering component has not even been created yet.
However, you can access the instance by passing a callback to next. The callback will be called when the navigation is confirmed, and the component instance will be passed to the callback as the argument:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component instance via `vm`
})
}
Also there are beforeRouteUpdate & beforeRouteLeave you might want use as well based on your needs, for these this is available ( but passing callback is not supported on these two )
beforeEnterand beforeRouteEnter are both guards with the same goal, the difference is where to use it.
beforeEnter is a guard you can define on a route's configuration object. https://router.vuejs.org/guide/advanced/navigation-guards.html#per-route-guard
beforeRouteEnter is a guard you define on your component.
https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards
Either way if you want to access you Vuex Store you should import it.
This could be an Example of your FeedPage component usign beforeRouteEnter
<script>
import Store from '../vuexstore.js'
export default{
beforeRouteEnter(to,from,next){
if(Store.state.isAllowed){
next()
} else {
next(false)
}
}
</script>
Don't forget to use next() to continue the navigation after the validation.

How to redirect to a different url inside the vue-router beforeRouteEnter hook?

I am building an admin page with Vue.js 2 and I want to prevent unauthenticated users from accessing the /admin route and redirect them to /login. For that I have used the In-Component Guard beforeRouteEnter in the Admin component like follows
...
beforeRouteEnter(to, from, next) {
if(userNotLogedIn) {
this.$router.push('/login');
}
}
The problem here is that this is not defined in beforeRouteEnter hook. So what's the proper way to access $router and redirect to a different url in this case ?
The documentation states that:
The beforeRouteEnter guard does NOT have access to this, because the
guard is called before the navigation is confirmed, thus the new
entering component has not even been created yet.
You can redirect to another page by calling next like this:
beforeRouteEnter(to, from, next) {
if(userNotLogedIn) {
next('/login');
}
}
Here is another way to accomplish the same result: So instead of using beforeRouteEnter on each protected route, you could define protected routes in your router configuration using a meta property, then use beforeEach hook on all the routes and check for protected routes and redirect to login page when needed:
let router = new Router({
mode: 'history',
routes: [
{
path: '/profile',
name: 'Profile',
component: Profile,
meta: {
auth: true // A protected route
},
},
{
path: '/login',
name: 'Login',
component: Login, // Unprotected route
},
]
})
/* Use this hook on all the routes that need to be protected
instead of beforeRouteEnter on each one explicitly */
router.beforeEach((to, from, next) => {
if (to.meta.auth && userNotLoggedIn) {
next('/login')
}
else {
next()
}
})
// Your Vue instance
new Vue({
el: '#app',
router,
// ...
})

Vue-router 2 changes route but does not update the view?

I have a login issue with website that uses:
Vue.js v2.0.3
vue-router v2.0.1
vuex v0.8.2
In routes.js I have a simple interceptor setup
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
And in login.vue,which handles the login page logic after using Google API only for login succeeds I call this:
this.login(userData).then(
() => this.$router.push(this.redirectToAfterLogin), // Login success
() => {} // Login failed
)
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
this.$router.push(this.redirectToAfterLogins)
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})}}
computed: {
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
return this.$route.query.redirect
}else{
return '/'
}
}
}
router.js
var VueRouter = require('vue-router')
// Router setup
export const router = new VueRouter({
linkActiveClass: "is-active",
mode: 'history',
saveScrollPosition: true,
routes: [
{ path: '', name: 'root', redirect: '/home' },
{ path: '/login', name: 'login', meta: { loadingNotRequired: true }, component: require('./pages/login.vue') },
{ path: '/logout', name: 'logout', meta: { loadingNotRequired: true }, component: require('./pages/logout.vue') },
{ path: '/home', name: 'home', title: 'Home', redirect: '/home/random', component: require('./pages/home.vue'),
children: [
{ path: 'random', name: 'random', meta: { requiresAuth: true }, title: 'Random', component: require('./pages/random.vue') }
]
}
]
})
// Redirect to login page if not logged In
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!router.app.auth.isUserLoggedIn) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
Now here this.login is just the call to vuex, to update the logged in user.
What happens is that after login, URL changes to /home, but the DOM does not update!
Only way that successfully changed the DOM was forcing location.reload() and that is not what I want to do, as it loses my dynamically loaded G scripts in Head.
Any idea on what to do to force the view to update DOM?
NOTE: it happens only on the first login of user, if he logs out and back-in, the redirecting is fine
Not a perfect solution may be, as it is going to recreate the component but it will work for every case when having same route & needs to update the component.
Just update the <router-view/> or <router-view></router-view> with
<router-view :key="$route.fullPath"></router-view>
Vue re-uses components where possible. You should use beforeRouteUpdate to react to a route switch that uses the same component.
I have the same problem "URL changes to /home, but the DOM does not update".
In my project, the tag "transition" maked the problem.
Hope it is helpful!
Maybe you should set the redirectToAfterLogin function into methods, like this it will be recalculated each times. The computed will be modified only if used v-model changed. To stick to the meaning of the function name, I would set the router push inside.
login.vue :
mounted: function(){
if (this.auth.isUserLoggedIn){
// Let's just redirect to the main page
// this.$router.push(this.redirectToAfterLogins)
this.redirectToAfterLogins()
}else{
Vue.nextTick(() => {
this.loadGooglePlatform()
})
}
},
// computed: {
methods: {
this.login(userData).then(
// () => this.$router.push(this.redirectToAfterLogin), // Login success
() => this.redirectToAfterLogin(), // Login success
() => {} // Login failed
),
redirectToAfterLogin: function() {
if (this.$route.query.redirect){
// return this.$route.query.redirect
this.$router.push(this.$route.query.redirect)
}else{
// return '/'
this.$router.push('/')
}
}
}
https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties
https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
"However, the difference is that computed properties are cached based on their dependencies. A computed property will only re-evaluate when some of its dependencies have changed. This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again."
methods vs computed and filters :
Access vue instance/data inside filter method

Categories

Resources