Vue 2 dynamic component import issue - vue.js

I have dynamic component called like this:
<component v-bind:is="loaderComponent" />
Then in my export default:
export default {
components: {
'loader': () => import(Vue.prototype.$VUE_LOADER_CONFIG.componentUrl)
}
},
computed: {
loaderComponent: function () {
console.log('1', Vue.prototype.$VUE_LOADER_CONFIG.componentUrl)
return 'loader'
}
}
That one console.log gives me the right path to the component, but it doesnt work, i need to pass the string instead of this variable to the import function to make this works.
import('../../Loaders/SomeLoader')
I get an error:
Vue warn]: Failed to resolve async component: function loader() {
return __webpack_require__("./src/base/components/Payment/Loader lazy recursive")(vue__WEBPACK_IMPORTED_MODULE_2__["default"].prototype.$VUE_LOADER_CONFIG.componentUrl);
}
Reason: Error: Cannot find module '../../Loaders/SomeLoader'
Any ideas?

It looks like Vue.prototype.$VUE_LOADER_CONFIG.componentUrl contains a relative path. Try to use an absolute path instead:
Vue.prototype.$VUE_LOADER_CONFIG.componentUrl = '#/base/components/Payment/Loader'

Related

vue3: control property with a timed function

First of all, I am a new vuejs developer and my purpose is to get acquainted with Vue, so, not going to use any external plugins or components.
I am writing a simple alert component, which looks like this:
<Alert :show="showAlert" />
I want the show property to return back to false after 2 seconds. How can I do this from inside the component (i.e., not in the page where this component is used). I tried this:
import { computed } from 'vue';
export default {
props: ['show'],
setup(props) {
const shown = computed(() => {
if (props.show) {
setTimeout(() => {
console.log("hiding the alert...")
props.show = false
}, 2000);
}
return props.show.value
})
return { shown }
}
};
the compiler said:
14:15 error Unexpected timed function in computed function vue/no-async-in-computed-properties
16:19 error Unexpected mutation of "show" prop vue/no-mutating-props
My rational is that the delay of alert should be controlled by the alert component (which could be changed by a prop), but not forcing the caller to write some thing like:
function Alert(delay) {
showAlert = true
setTimeout(() => showAlert = false, delay)
}
There are 2 errors.
First vue/no-mutating-props, props are read only so you are not supposed to change it from within the component. It is still possible to change props from outside the component and pass down to it.
For this you should copy the value of props to your data()
data() {
return {
showAlert
}
}
You should be able to update showAlert with no problem.
The second error vue/no-async-in-computed-properties, you cannot write async function inside computed(), so the alternative is to use watch instead.

How to pass a prop with a default value in case the prop is empty without using the prop's "default" property

I'm using a third party multi-language package where translation values can only be obtained/used from component's template, not from component's logic (at least I can't find how to the latter).
I need to pass such translation value in some component as a default prop value:
:placeholder="{ propValue ? propValue : $t('value') }
If placeholder is explicitly specified then use that. If not, use $t('value') instead. What's the right way to write this?
I tried this in reaction to #Michael's answer:
import i18n from "#/utilities/i18n";
export default {
data() {
return {
test: i18n.t('register.agreeTermsCaptionLink')
};
}
};
Getting this error:
_utilities_i18n__WEBPACK_IMPORTED_MODULE_7__.default.t is not a function
Solution 1
Just remove brackets from prop expression: :placeholder="propValue ? propValue : $t('value')"
Sotution 2
More complicated but helps to keep templates cleaner...
where translation values can only be obtained/used from component's template, not from component's logic
With vue-i18n you can of course obtain translation directly in code by using $t function injected into your component instance like this: this.$t('key.to.translation')
Only problem is, this is not possible to use to initialize props default values because this is just not available there.
But $t in fact just returns instance function of VueI18n global object. So if you setup your VueI18n like this:
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
const messages = {
en: {
messages: {
placeholder: "Placeholder"
}
},
cz: {
messages: {
placeholder: "Zástupný symbol :)"
}
}
};
export default new VueI18n({
locale: "en",
messages
});
You can do this to provide translation as default value of your prop:
import i18n from "../i18n";
export default {
name: "HelloWorld",
props: {
placeholder: {
type: String,
// default: this.$t("value") - Wont work as `this` is not Vue instance
default: i18n.t("messages.placeholder")
}
}
};
Demo
You can set default value to prop like this:
props: {
propValue: {
type: String,
default: this.$t('value')
}
}
With that in your template you need just to assign that value like: :placeholder="propValue"
Is that what you trying to achive?
Good luck!

Resolve Vue component names on the fly

I'm searching for a way to resolve Vue (sfc) component names 'on the fly'. Basically I'm getting kind of component names from a backend and need to translate them into real component names on the frontend. Something like this:
<text/> => /components/TextElement.vue
or
<component v-bind:is="text"></component> => => /components/TextElement.vue
The idea is providing a global function that maps the backend names to the frontend names. I don't want to do this in every place where those components are used, so maybe there is a kind of hook to change the names dynamically?
Thanks a lot,
Boris
You could create a wrapper <component> that maps the component names and globally registers the specified component on the fly.
Create a component with a prop (e.g., named "xIs") for the <component>'s is property:
export default {
props: {
xIs: {
type: String,
required: true
}
},
};
Add a template that wraps <component>, and a data property (e.g., named "translatedName") that contains the mapped component name (from backend to front-end name):
export default {
data() {
return {
translatedName: ''
}
}
};
Add a watcher on the prop that will register a component by the name indicated in the prop value:
import componentNames from './component-names.json';
export default {
watch: {
xIs: {
immediate: true, // run handler immediately on current `xIs`
async handler(value) {
try {
const name = componentNames[value]; // 1. lookup real component name
if (name) {
Vue.component(name, () => import(`#/components/${name}`)); // 2. register component
this.translatedName = name; // 3. set name for
}
} catch (e) {
console.warn(`cannot resolve component name: ${value}`, e);
}
}
}
}
}

Property or method "$v" is not defined using Vuelidate

Error:
[Vue warn]: Property or method "$v" is not defined on the instance but
referenced during render. Make sure that this property is reactive,
either in the data option, or for class-based components, by
initializing the property. See:
https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
found in
---> at resources\assets\js\components\products\Product_create.vue
I'm using Vue.js and Vuelidate as validator,
I've copied and pasted this code from here https://jsfiddle.net/Frizi/b5v4faqf/ but it still doesn't work :
Vue Component :
<template >
<input v-model="$v.text.$model" :class="status($v.text)">
<!-- <pre>{{ $v }}</pre> -->
</template>
<script>
import { required, minLength, between } from 'vuelidate/lib/validators'
export default {
data() {
return {
text: ''
}
},
validations: {
text: {
required,
minLength: minLength(5)
}
},
methods: {
status(validation) {
return {
error: validation.$error,
dirty: validation.$dirty
}
}
}
}
</script>
App.js
require('./bootstrap');
window.Vue = require('vue');
window.VueRouter = require('vue-router').default;
window.VueAxios = require('vue-axios').default;
window.Axios = require('axios').default;
window.VueFormWizard = require('vue-form-wizard');
window.Vuelidate = require('vuelidate').default;
import 'vue-form-wizard/dist/vue-form-wizard.min.css';
Vue.use(VueRouter,VueAxios,axios,VueFormWizard,Vuelidate);
const ProductOptionCreate = Vue.component('po-create',require('./components/products/ProductOption_create.vue'));
const ProgressModal = Vue.component('vue-modal',require('./components/ProgressModal.vue'));
const ProductIndex = Vue.component('product-list',require('./components/products/Product_index.vue'));
const productshow = Vue.component('productshow',require('./components/products/ProductOption_show.vue'));
const ProductCreate = Vue.component('product-create',require('./components/products/Product_create.vue'));
const app = new Vue({
el:'#app',
});
What's wrong with this code?
The problem is that $v is not defined at a component level, and it is because of the order of your components, you need to reorder them like so:
// ... other stuff
import 'vue-form-wizard/dist/vue-form-wizard.min.css';
Vue.use(Vuelidate);
const ProductOptionCreate = // ... rest of your components
I think the problem was that the validation was declared within the data property of the component and not as a direct property of the component.
So
export default {
validations: {
text: {
required,
minLength: minLength(5)
},
data() {
return {
text: ''
}
},
},
........
instead of
export default {
data() {
return {
text: ''
}
},
validations: {
text: {
required,
minLength: minLength(5)
}
The reason $v is not available on your instance is because you haven't instantiated the Vuelidate plugin. And that's because you tried to streamline the call to Vue.use().
Your current Vue.use() call only instantiates the first plugin (VueRouter) and passes VueAxios plugin object as VueRouter's config object. All subsequent arguments are ignored.
To streamline your calls to Vue.use(), you can't simply add more arguments to it.
Instead of this erroneous syntax (which breaks instantiation of all but first plugin):
Vue.use(VueRouter, VueAxios, axios, VueFormWizard, Vuelidate);
... you could use:
[[VueRouter], [VueAxios, axios], [VueFormWizard], [Vuelidate]]
.forEach(args => Vue.use(...args));
With Typescript: unfortunately, TS parser wrongly includes 0 arguments as a possible outcome of the spread operator in the above case, so you'd need to suppress it using:
[
[VueRouter],
[VueAxios, axios],
[VueFormWizard],
[Vuelidate]
/* #ts-ignore: TS2557, TS wrong about array above having empty members */
].forEach(args => Vue.use(...args));
... at least for now ("typescript": "^3.9.7").
The above syntax is the exact equivalent of:
Vue.use(VueRouter);
Vue.use(VueAxios, axios);
Vue.use(VueFormWizard);
Vue.use(Vuelidate); // <= this is what you were missing, basically
// but your syntax also broke VueAxios and VueFormWizard install
On a personal note: although a tad more repetitive, I actually find the Vue.use() syntax cleaner in this case (more readable).
The validations should be defined in a component in order to initialize this.$v
I had a typo and didn't realized I was declaring validations inside methods.
methods: {
validations: {
isUScitizen: { required },
},
}
And I was trying to access this.$v which was undefined because the component didn't had validations defined.
You must specify this.$v and there will be no error

is it possible to specify which component should be used on router.go() in VueJS

In VueJS im trying to setup a scenario where the component used is determined by the url path without having to statically map it.
e.g.
router.beforeEach(({ to, next }) => {
FetchService.fetch(api_base+to.path)
.then((response) => {
router.app.$root.page = response
// I'd like to specify a path and component on the fly
// instead of having to map it
router.go({path: to.path, component: response.pageComponent})
})
.catch((err) => {
router.go({name: '404'})
})
})
Basically, I'd like to be able to create a route on the fly instead of statically specifying the path and component in the router.map
Hope that make sense. Any help would be appreciated.
I think that what you're trying to archive is programmatically load some component based on the current route.
I'm not sure if this is the recommended solution, but is what comes to my mind.
Create a DynamicLoader component whit a component as template
<template>
<component :is="CurrentComponent" />
</template>
Create a watch on $route to load new component in each route change
<script>
export default {
data() {
return {
CurrentComponent: undefined
}
},
watch: {
'$route' (to, from) {
let componentName = to.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
},
beforeMount() {
let componentName = this.$route.params.ComponentName;
this.CurrentComponent = require(`components/${componentName}`);
}
}
</script>
Register just this route on your router
{ path: '/:ComponentName', component: DynamicLoader }
In this example I'm assuming that all my componennt will be in components/ folder, in your example seems like you're calling an external service to get the real component location, that should work as well.
Let me know if this help you
As par the documentation of router.go, you either need path you want to redirect to or name of the route you want to redirect to. You don't the component.
Argument of router.go is either path in the form of:
{ path: '...' }
or name of route:
{
name: '...',
// params and query are optional
params: { ... },
query: { ... }
}
so you dont need to return component from your API, you can just return path or name of route, and use it to redirect to relevant page.
You can find more details here to create named routes using vue-router.