Vue Router Resets Global Data - vue.js

Im trying to store user data globally using a Vue mixin: (main.js)
import Vue from 'vue';
import Resource from 'vue-resource'
import App from 'App';
import router from 'router/index';
Vue.use(Resource);
Vue.mixin({ //globals
delimiters: ["[[", "]]"],
http: {
root: 'http://127.0.0.1:5000/'
},
data: function() {
return {
user: {
authorized: false,
username: '',
password: '',
avatar: '',
entry: '',
skill: '',
arena: {
id: '',
start: false,
votes: '',
}
}
}
}
});
new Vue({
router: router,
el: '#app',
components: {
App
},
template: '<App/>'
});
I get the data from a login page just fine: (part of Login.vue)
import Vue from 'vue';
export default {
name: 'Login-Page',
data() {
return {
message: 'Hello Vue!'
}
},
methods: {
_make_basic_auth(user, pass) {
var tok = user + ':' + pass;
return "Basic " + btoa(tok);
},
_fetch_user(protocol) {
this.message = 'waiting...';
var auth = this._make_basic_auth(this.user.username, this.user.password);
Vue.http.headers.common['Authorization'] = auth;
this.$http[protocol]('api/u/' + this.user.username).then(response => {
this.message = "Success";
if (response.body.authorized) {
this.user = {...this.user, ...response.body};
setTimeout(() => {
this.$router.push({
name: 'Profile',
params: {
id: this.user.username
}
});
}, 1000);
}
}, response => {
this.message = response.body;
console.log(response.status + " " + response.body);
});
},
register() {
this._fetch_user('post');
},
login() {
this._fetch_user('get');
}
}
}
The data is just reset on redirect: (part of Main.vue)
import Profile from 'components/Profile'
export default {
name: "Main-Page",
methods: {
enterArena() {
this.$http.get('api/match').then(response => {
console.log(response.body);
this.user.arena = {...response.body, ...this.user.arena};
this.$router.push({
name: "Arena",
params: {'id': response.body.id}
});
}, error => {
console.log(error.status + " " + error.body);
});
}
},
created() {
console.log(this);
console.log(this.user);
if (!this.user.authorized)
this.$router.push({
name: "Login"
});
}
}
It was working before, here is my old repo https://github.com/Jugbot/Painter-Arena-Web-API/tree/6f3cd244ac17b54474c69bcf8339b5c9a2e28b45
I suspect that the error is from my new arrangement of components in my Router or flubbed this references.
index.js:
routes: [
{
path: '',
name: 'Main',
component: Main,
children: [
{
path: '/arena/:id',
name: 'Arena',
component: Arena
},
{
path: '/u/:id',
name: 'Profile',
component: Profile
}
]
},
{
path: '/login',
name: 'Login',
component: Login
},
{
path: '/404',
component: NotFound
},
{
path: '*',
redirect: '/404'
},
],
mode: 'hash'
Update:
Problem is still unsolved but as a workaround I just moved all mixin data to the $root instance and that managed to work.

I recommend you to use vuex for better state management. It is complicated to use mixins as a data storage for a vue application. Using vuex is convenient way to manipulate dynamic or static data across the application and will not be deleted in destroy hook upon exiting on a component.

Related

TypeError: a.then is not a function while compiling in production

I'm trying to compile my vue app in production with npm run build (which is vite build).
After that I try to serve my app on my server with the /dist folder and everything seems to be working perfectly, I'm able to send fetch request, click on various links etc.
Unfortunately, after logging in and when I should be redirected, I'm getting the error
TypeError: a.then is not a function while compiling in production
and
Uncaught (in promise) TypeError: a.then is not a function"
Everything just works perfectly fine while I'm in dev mode, it's just not working in production.
It seems to be linked to the router
This is the code for my Router :
const state = reactive({
token: localStorage.getItem("token"),
userAdmin: false,
userRestaurateur: false,
userDeliverer: false
});
if (state.token) {
const user = JSON.parse(atob(state.token.split('.')[1]))
state.userAdmin = Object.values(user.roles).includes('ROLE_ADMIN');
state.userRestaurateur = Object.values(user.roles).includes('ROLE_RESTAURANT');
state.userDeliverer = Object.values(user.roles).includes('ROLE_DELIVERER');
}
const router = createRouter({
history: createWebHistory('/'),
routes: [
{
path: '/',
name: 'home',
component: function () {
if (state.token && state.userAdmin) {
return Users
} else if (state.token && state.userRestaurateur) {
return HomeRestaurateur
}else if (state.token && state.userDeliverer) {
return Commands
}else {
return Home
}
}
},
{
path: "/login",
name: "login",
component: Login,
},
{
path: "/forgot-password",
name: "forgot_password",
component: ForgotPassword,
},
{
path: "/reset-password/:token",
name: "reset_password_token",
component: ResetPassword,
},
{
path: "/register",
name: "register",
component: Register,
},
{
path: "/profile",
name: "editProfile",
component: editProfile,
},
{
path: "/Restaurant/:id/Menu",
name: "Meals",
component: Meals,
},
{
path: "/admin/users",
name: "admin_users",
component: function () {
if (state.userAdmin) {
return Users
} else {
return Error403
}
}
},
{
path: "/admin/restaurants",
name: "admin_restaurants",
component: function () {
if (state.userAdmin) {
return Restaurants
} else {
return Error403
}
}
},
{
path: "/admin/restaurants_request",
name: "admin_restaurants_request",
component: function () {
if (state.userAdmin) {
return RestaurantsRequest
} else {
return Error403
}
}
},
{
path: "/restaurants/new",
name: "create_restaurants",
component: CreateRestaurant,
},
{
path: "/admin/reports",
name: "admin_reports",
component: function () {
if (state.userAdmin) {
return Reports
} else {
return Error403
}
}
},
{
path: "/orders",
name: "orders",
component: Commands,
},
{
path: "/:pathMatch(.*)*",
name: "not_found",
component: Error404,
}
],
});
I tried checking if other methods were working correctly, tried to change server, nothing just seems to work.

Push route to parent component inside a function (Vue)

I feel like I'm missing something very obvious but I can't figure it out. Please help!
I have the following routes defined:
const routes = [
{
path: '/',
name: 'Login',
component: () => import('../views/Login.vue'),
meta: {
authRedirect: true
}
},
{
path: '/companies',
name: 'Companies',
component: () => import('../views/Companies.vue'),
meta: {
requiresAuth: true
}
},
{
path: '/companies/:id',
name: 'Company',
component: () => import('../views/Company.vue'),
meta: {
requiresAuth: true
}
},
{
path: '*',
name: '404',
component: () => import('../views/404.vue')
}
]
Then I have the following in my component:
export default {
name: 'Company',
data() {
return {
company: {}
}
},
methods: {
getCompanyDetails: function() {
let self = this
axios.get('/api/companies/' + this.$route.params.id).then(function(response) {
self.company = response.data
}).catch(function() {
self.$router.push('companies')
})
}
},
created() {
this.getCompanyDetails()
}
}
Essentially everything is working if the API returns data, but inside the catch function I'm trying to push the route back to /companies. But it's redirecting to /companies/companies. How do I redirect it to the correct route?
Did you tried $router.push('/companies') (with a / in the path) ?
Also, you can use $router.push({ name: 'Companies' }) if you want to make it more clear, it will match the name defined in your routes.

Computed property depends on vuex store. How to update the cached value?

The value of this.$store.state.Auth.loginToken is modified by one of its child components. The initial value of this.$store.state.Auth.loginToken is undefined. But still, the update in its value has no effect in the cached value of navItems thus it always returns the second value.
computed: {
navItems () {
return this.$store.state.Auth.loginToken != undefined ?
this.items.concat([
{ icon: 'add', title: 'Add new journal entry', to: '/' },
{ icon: 'power_settings_new', title: 'Logout', to: '/logout'}
]) :
this.items.concat([
{ icon: 'play_arrow', title: 'Login', to: '/login' }
])
}
}
Is there a better way to keep a watch on this.$store.state.Auth.loginToken so that it can be used same as navItems?
I created a basic example of how you can use vuex getters and Auth token (codepen):
const mapGetters = Vuex.mapGetters;
var store = new Vuex.Store({
state: {
Auth: {
loginToken: ''
},
menuItems: [
{ icon: 'home', title: 'Home', to: '/' },
{ icon: 'about', title: 'About', to: '/about' },
{ icon: 'contact', title: 'Contact', to: '/contact' }
]
},
mutations: {
SET_LOGIN_TOKEN(state, data) {
state.Auth.loginToken = 1
}
},
getters: {
menuItems(state, getters) {
if(state.Auth.loginToken !== '') {
return state.menuItems.concat([{
icon: 'profile', title: 'Profile', to: '/profile'
}])
}
return state.menuItems
},
loggedIn(state) {
return state.Auth.loginToken !== ''
}
},
actions: {
doLogin({commit}) {
commit('SET_LOGIN_TOKEN', 1)
}
}
});
new Vue({
el: '#app',
store,
data: function() {
return {
newTodoText: "",
doneFilter: false
}
},
methods: {
login() {
this.$store.dispatch('doLogin')
}
},
computed: {
...mapGetters(['menuItems', 'loggedIn'])
}
})
This is just an example so you can ignore the actual login action. Also, the store should be a directory, the getters, mutations and actions should have their own files which are then imported in the index.js in the store like in this example

Angular 5 Parent reloads when Child route changes

I am having a problem where my parent component (LoggedInComponent) is getting reloaded every time one of the child components changes (child route change).
I have searched high and low for an answer but can't seem to find anything suitable to my situation.
Here is my app-routing.module.ts
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{
path: '', component: LoggedInComponent, canActivateChild: [AuthGuard], children: [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: DashboardComponent },
{ path: 'groups', component: GroupsComponent, data: { role: [PermissionEnum.Groups_View] } },
{ path: 'groups/edit/:id', component: GroupDetailComponent, data: { role: [PermissionEnum.Groups_Edit] } },
{ path: 'groups/create', component: GroupDetailComponent, data: { role: [PermissionEnum.Groups_Create] } },
{ path: 'users', component: UsersComponent, data: { role: [PermissionEnum.Users_View] } },
{ path: 'users/edit/:id', component: UserDetailComponent, data: { role: [PermissionEnum.Users_Edit] } },
{ path: 'users/create', component: UserDetailComponent, data: { role: [PermissionEnum.Users_Create] } },
{ path: 'profile', component: ProfileComponent },
{ path: 'profile/:tabindex', component: ProfileComponent },
{ path: 'settings', component: SettingComponent, data: { role: [PermissionEnum.Global_Settings_View] } },
{ path: 'external-login/:result', component: ExternalLoginProvidersComponent },
{ path: 'permissions/:id/:type', component: PermissionsComponent, data: { role: [PermissionEnum.Users_AssignPermissions] } },
{ path: 'permission-denied', component: PermissionDeniedComponent },
{ path: 'reference-data/:type', component: ReferenceDataComponent, data: { role: [PermissionEnum.Sms_Template_View] } },
{ path: 'reference-data/:type/edit/:id', component: ReferenceDataDetailsComponent, data: { role: [PermissionEnum.Sms_Template_Edit] } },
{ path: 'reference-data/:type/create', component: ReferenceDataDetailsComponent, data: { role: [PermissionEnum.Sms_Template_Create] } },
{ path: 'tenants', component: TenantsComponent, data: { role: [PermissionEnum.Tenant_View] } },
{ path: 'tenants/edit/:id', component: TenantDetailComponent, data: { role: [PermissionEnum.Tenant_Edit] } },
{ path: 'tenants/create', component: TenantDetailComponent, data: { role: [PermissionEnum.Tenant_Create] } },
{ path: 'sms-campaigns', component: SmsCampaignsComponent, data: { role: [PermissionEnum.SmsCampaign_View] } },
{ path: 'sms-campaigns/create', component: CreateSmsCampaignComponent, data: { role: [PermissionEnum.SmsCampaign_Create] } },
{ path: 'sms-campaigns/details/:id', component: SmsCampaignDetailsComponent, data: { role: [PermissionEnum.SmsCampaign_View] } },
{ path: 'document-library', component: LibraryDocumentsComponent },
{ path: 'report-management', component: ReportManagementComponent },
{ path: 'report-management/create', component: CreateReportComponent },
{ path: 'report-management/:id', component: IdpComponent },
{ path: 'report-management/edit/:id', component: ReportDetailsComponent },
{ path: 'report/:reportName', component: ReportComponent }
]
}
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
I have the main router-outlet in my app.component.html which after loggin in takes you to the LoggedInComponenet which has the header, footer, left menu and another router-outlet for the children.
This is my LoggedIn.componenent.html
<app-header></app-header>
<div class="m-grid__item m-grid__item--fluid m-grid m-grid--ver-desktop m-grid--desktop m-body">
<app-left-menu></app-left-menu>
<div *ngIf="loading">
<app-loading-indicator></app-loading-indicator>
</div>
<div class="center-display" *ngIf="childrenLoadingAllowed">
<router-outlet class="m-grid__item m-grid__item--fluid m-wrapper" [ngClass]="{ hidden: loading }"></router-outlet>
</div>
</div>
<app-footer></app-footer>
I then have my LoggedIn.component.ts
import { Component, OnInit } from '#angular/core';
import { BaseComponent } from '../shared/base.component';
#Component({
selector: 'app-logged-in',
templateUrl: './logged-in.component.html',
styleUrls: ['./logged-in.component.css']
})
export class LoggedInComponent extends BaseComponent implements OnInit {
public loading = true;
public childrenLoadingAllowed = false;
constructor() {
super();
}
ngOnInit() {
this.layoutService.setLoadingEvent
.subscribe((res: boolean) => {
if (this.loading !== res)
this.loading = res;
});
}
}
And then finally here is the left-menu which keeps reloading when i load a child
import { Component, OnInit, ViewEncapsulation } from '#angular/core';
import { BaseComponent } from '../../shared/base.component';
import { PermissionEnum, LookupClient, LookupType, LookUpDto } from '../../../services/web-api-generated';
#Component({
selector: 'app-left-menu',
templateUrl: './left-menu.component.html',
styleUrls: ['./left-menu.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class LeftMenuComponent extends BaseComponent implements OnInit {
public hasReports = false;
public reports: Array<LookUpDto> = new Array<LookUpDto>();
constructor(private lookupClient: LookupClient) {
super();
this.loadReportMenuItems();
}
ngOnInit() {
this.layoutService.rebuildReportMenu
.subscribe(res => {
this.loadReportMenuItems();
});
}
private loadReportMenuItems(): void {
this.lookupClient.getLookUpValues(LookupType.MunicipalReports)
.subscribe((res: Array<LookUpDto>) => {
this.reports = res;
this.reports.forEach(element => {
element.value = element.value.replace(/\s+/g, '-').toLocaleLowerCase();
});
this.hasReports = res.length > 0;
});
}
}
I fixed the problem by moving the api call to a service with a variable there and only loading the data if its not already set or if the force variable is passed through.
I believe this is a bug as mentioned here: https://github.com/angular/angular/issues/18374
yes, canActivateChild reloads whole parent component while changing between child routes

Passing props to nested route

I have the following routes in my VueJS app:
const routes = [
{
path: '/',
component: PlacesList
},
{
name: 'place',
path: '/places/:name',
component: CurrentWeather,
children: [
{
name: 'alerts',
path: 'alerts',
component: ForecastAlerts
},
{
name: 'forecast',
path: 'forecast',
component: ForecastTimeline
}
]
}
];
ForecastTimeline receives props passed from CurrentWeather:
export default {
name: 'ForecastTimeline',
props: ['forecastData'],
components: {
ForecastDay
}
}
When I navigate to /places/:name/forecast from /places/:name everything works well. However, when I try to reach /places/:name/forecast directly I get a Cannot read property 'summary' of undefined error. This occurs because forecastData is fetched asynchronously within the CurrentWeather component. What is the proper way to handle asynchronous properties passed to a nested route?
Have you tried not displaying the subroutes components while fetching the data ?
Something like :
export default {
name: 'CurrentWeather',
data(){
return {
loading: true
}
},
created(){
this.fetchForecastData().then(() => {
this.loading = false;
});
},
methods: {
fetchForecastData() {
return fetch('http://..');
}
}
}
In CurrentWeather template :
<router-view v-if="!loading"></router-view>