Vue 3 Dynamic import files - javascript

I'm trying to build Vue 3 dynamic loading JSON files, in order to create a website builder. So each page is defined as a JSON file, in order to allow the user to change the JSON file from the backend (all components are defined in that file).
When I'm loading every other route than auth, it's working fine, but Auth is loading twice (the load_components function).
Here is the example of the routes. Most of the pages will have a loadParent component, but there are a few that will not. And loadParent for now has only <router-view /> in it:
// Simple page
{
path: "/home",
name: "home",
component: () => import("../loadPage.vue"),
},
// Pages with parent
{
name: 'auth',
path: '/auth',
meta: {
title: "Some users" ,
adminOnly: true
},
component: () => import("../loadParent.vue"),
children: [
{
path: '',
name: 'login',
props: (route: any) => ({}),
component: () => import("../loadPage.vue"),
}
]
}
So here is how template looks like:
<template>
<component v-for="block in components" :is="{...block.component}" :key="Math.random()" :vars="block.props"></component>
</template>
And in the setup script I have defined ref where is stored path of the file, like:
const json: any = ref();
Because every file is defined as route name, I'm using import in setup:
json.value = import('../pages/' + route.value + '.json');
So here is how the function how I'm actually loading the components:
const load_components = async () => {
console.log('load page components ')
// Get file for load
Promise.resolve(json.value).then(function (e: any) {
// Reset previous page pages
components.value = [];
// For each component for the page and register them under new const of the pages
e.default.components.forEach((c: any) => {
components.value.push({
"component": defineAsyncComponent(() => import('./templates/' + template.value + '/' + c.component)),
"props": c.props
})
})
}).catch((e: any) => {
// If component can't be loaded - Delete for production
console.log(e + ' cannot be loaded!');
});
}
So in order to trigger the function, I'm doing something like:
onBeforeMount(async() => {
json.value = import('../pages/' + route.value + '.json');
// Initiate component load
await load_components()
})
// /** Watch when client changes a page to load new json for next page - This function is for pages that has same component */
watch(router, async (value: any) => {
json.value = import('../pages/' + value.name + '.json');
// There is no need for that if I load it really dynamically
await load_components()
})
Maybe just the approach is not correct, what do you think?

Related

Angular SSR, Dynamic Page Won't Load - Status 504

Code: http://github.com/dbots-co/website
Directly connecting to a dynamic route (dashboard, bot page), seems to not work. However, directly connecting to docs does work.
This only happens when using Angular 10 Universal SSR.
Not Working: https://dbots.co/bots/774811448576573480
Working: https://dbots.co/docs/get-started
bot-page.component.ts:
async ngOnInit() {
await this.service.init();
this.user = this.service.getBot(this.id);
this.bot = this.service.getSavedBot(this.id);
if (!this.user || !this.bot)
return this.router.navigate(['']);
this.seo.setTags({
description: this.bot.listing.overview,
titlePrefix: this.user.username,
titleSuffix: 'DBots',
url: `bots/${this.id}`
});
this.analytics.botPageView({ botId: this.user.id });
this.themeService.setNavbarBackground('var(--background-secondary)');
document
.querySelector('.navbar')
.setAttribute('style', `margin-bottom: -5px;`);
}
app-routing.module.ts:
const routes: Routes = [
...
{ path: 'bots/:id', component: BotPageComponent },
...
];

VUE JS Dynamic background image after axios request

I'm trying to display a background image that it's path needs to be loaded through an API.
The plan is: From a main grid of links, click one and display a background image according to the one clicked.
As of now I am using axios to query my API which sends the data I need. I have the following script part on my component.
<script>
import axios from 'axios'
const lhost = require("#/config/global").host;
let championData;
export default {
name: 'IndividualChampion',
props: {
},
data: () => ({
champions: [],
verPersonagem: mdiMovieOpen,
}),
computed: {
},
created: async function() {
try {
let champion = this.$route.fullPath.split('/')[2];
let response = await axios.get(lhost + "/champion/" + champion + '/full');
championData = response.data
console.log(championData)
let background = '#/assets' + championData['skins']['0']['splash'].replace('.png','.jpg')
}
catch (e) {
return e;
}
},
methods: {
}
}
</script>
And this is my HTML
<template>
<div :style="{ backgroundImage: `url(${require(background)})` }">
</div>
</template>
I have searched but can't seem to find a solution in which the background image is loaded and, when loaded, is presented.
Can someone help?
Judging from your use of '#/assets', you seem to be using webpack with a resolve alias. The expression require(background) is not enough for webpack to determine what files it needs to add to your bundle.
You can help Webpack by specifying the directory that you want to load your file from. All you have to do is take out '#/assets/' from the background variable and use it directly in the require call so that Webpack can see it.
<template>
<div v-if="background" :style="{ backgroundImage: `url(${require('#/assets/' + background)})` }">
</div>
</template>
<script>
import axios from 'axios'
const lhost = require("#/config/global").host;
let championData;
export default {
name: 'IndividualChampion',
props: {
},
data: () => ({
champions: [],
verPersonagem: mdiMovieOpen,
background: ''
}),
computed: {
},
created: async function() {
try {
let champion = this.$route.fullPath.split('/')[2];
let response = await axios.get(lhost + "/champion/" + champion + '/full');
championData = response.data
console.log(championData)
this.background = championData['skins']['0']['loading'].replace('.png','.jpg')
}
catch (e) {
return e;
}
},
methods: {
}
}
</script>
It will bundle every possible file inside the directory, though.
You can read more about it here: https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import

Next Js Custom Routes and SSR

I am using apollo with next and recently I noticed that custom routes breaks SSR. Usually if you navigate through pages apollo caches the query and when you are on the page the next time, it serves everything from cache. However with custom routes, the cache is never used.
I also noticed that when I click on these pages, an error flashes in the console. But it goes away very fast and I wasn't able to copy it here.
Server.js
//
server.get('/about-us', (req, res) => app.render(req, res, '/about'));
server.get('/about', (req, res) => res.redirect(301, '/about-us'));
Menu Click Handler
const navigate = link => () => {
Router.push(link);
};
Menu Items
export const menu = [
{
name: 'Home',
url: '/',
},
{
name: 'Catalogs',
url: '/catalogs',
},
{
name: 'Shop',
url: '/shop',
},
{
name: 'Wholesale',
url: '/wholesale',
},
{
name: 'About Us',
url: '/about-us',
prefetch: true,
},
{
name: 'Contact Us',
url: '/contact-us',
prefetch: true,
},
];
Based on a suggestion from nextjs spectrum I tried prefetching custom pages in the TopNav Component but it didn't work.
const prefetch = url => {
if (process.browser) {
console.log('prefetching these urls', url);
Router.prefetch(url);
}
};
useEffect(() => {
menu.forEach(menuItem => {
if (menuItem.prefetch) {
prefetch(menuItem.url);
}
});
}, []);
I was able to figure out the problem. This is not really well documented but you need to prefetch the component. So for my case instead of prefetching /about-us I should have prefetched /about.
That's why there is as prop in the link component. Nextjs 9 just got released which fixes this issue.
https://nextjs.org/blog/next-9#dynamic-route-segments
For nextjs 9 you can save your file as [pid].js and it will catch all paths in a specific route. i.e for /products/test-product you have to create folder products and inside that add [pid].js.
I needed to query for product based on slug so I added this and voila, I have access to the slug inside my component.
Product.getInitialProps = async ({ query }) => {
return { slug: query.pid };
};
These issues were pretty frustrating before next 9 but it's heavily simplified and it helped me fully remove server.js.

Axios returns undefined in nested Vue JS (Laravel API)

I'm having a lot of trouble with Axios when using nested routes in Vue JS.
I have found that if my component is at the root ("/"), as with the "Accounts" component below, then Axios loads the data correctly, no problems here.
But if I went to the "Campaigns" component, which is nested at "/accounts/:account_id" then Axios stops working. In fact, it doesn't return any data at all. However, the API is valid, as Postman correctly returns the data.
So whenever I move a component into a nested URL, Axios stops working. I have no idea why this is happening, and I cannot find any solutions online. I'm sure it must be simple, but I can't see it.
app.js (Includes routes)
const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
name: 'accounts',
component: Accounts
},
{
path: '/accounts/:account_id',
name: 'campaigns',
component: Campaigns
},
],
});
Campaigns Component
<script>
import axios from 'axios'
export default {
data() {
return {
accountID: null,
campaigns: [],
campaignsMeta: {},
};
},
mounted() {
this.accountID = this.$route.params.account_id;
this.fetchCampaigns();
},
methods : {
fetchCampaigns(page = 1) {
const AuthStr = 'Bearer '. concat(this.apitoken);
axios.get("api/account/" + this.accountID + "?page=" + page)
.then(({data}) => {
this.campaigns = data.data;
this.campaignsMeta = data.meta;
});
}
},
}
</script>
For anyone who is experiencing a similar problem in the future, I made the error of using a relative URL and not an absolute URL for the Axios Get request.
Original Code
axios.get("api/account/" + this.accountID + "?page=" + page)
.then(({data}) => {
this.campaigns = data.data;
this.campaignsMeta = data.meta;
});
Correct Code
axios.get("/api/account/" + this.accountID + "?page=" + page)
.then(({data}) => {
this.campaigns = data.data;
this.campaignsMeta = data.meta;
});
Note the "/" at the start of "/api/account" indicating an absolute path, not relative.
The reason this is important is because:
For relative URLs, the API request would be appended to the end of the current page URL, so the API request on the Accounts component would have been: /accounts/:account_id/api/account (which is wrong!)
For absolute URLs, the API request is made from the domain name, so the API request on the Accounts component is: domain.com/api/account (correct!)
Thanks!

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