Resolve Vue component names on the fly - vue.js

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);
}
}
}
}
}

Related

How to add emit information to a component dynamically generated using Vue.extent()?

I'm dynamically generating instances of my child component "Action.vue" using Vue.extent() this way:
let ActionClass = Vue.extend(Action)
let ActionInstance = new ActionClass({
store
})
ActionInstance.$mount()
this.$refs.actions.appendChild(ActionInstance.$el)
This works fine. However, besides access to the store, child component also needs to emit an event (in response to user interaction with one of its elements) for the parent component to execute a method.
How to achieve this?
You can use instance.$on method to add eventListenersdynamically :
Consumer
import Dummy from "./Dummy.vue";
import Vue from "vue";
export default {
name: "HelloWorld",
props: {
msg: String
},
methods: {
mount: function() {
const DummyClass = Vue.extend(Dummy);
const store = { data: "some data" };
const instance = new DummyClass({ store });
instance.$mount();
instance.$on("dummyEvent", e => console.log("dummy get fired", e));
this.$refs.actions.appendChild(instance.$el);
}
}
};
Child component
export default {
methods: {
fire: function() {
console.log("fired");
this.$emit("dummyEvent", { data: "dummyData" });
}
}
};
Here is the Sandbox
https://v2.vuejs.org/v2/guide/components-custom-events.html
https://v2.vuejs.org/v2/api/#Options-Lifecycle-Hooks
You can use a lifecylce hook (for example: mounted) to emit the event when the child has been created.
you can listen to the events as documented in the documentation.
the store can be reached through this.$store.

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!

Passing values between exported and non-exported part of script

How to pass values between exported part of a script and non-exported one?
The construction looks like this:
<script>
// PART 1:
import { EventBus } from './event-bus.js';
EventBus.$on('value-received', value => {
this.receivedValue = value;
});
// PART 2:
export default {
data() {
return {
receivedValue: ''
}
},
watch: {...},
methods: {...}
}
</script>
How can I get the value assigned to receivedValue variable and made usable by Vue methods?
Because your EventBus.$on call uses an arrow function, the this it references is the this in scope at the time of the EventBus call.
If you are okay with all instances of your event having the same receivedValue, you could redirect the values received to an object in the scope of your file:
var shared = { receivedValue: '' };
EventBus.$on('value-received', value => {
shared.receivedValue = value;
});
export default {
data() { return shared; }
watch: ...,
methods: ....
}
Vue.js will register a handler to react on changes to the object you returned, ie. shared.
If for some reason you want a separate event stream per instance of your component, you need to create a new object inside the data() function and have your event bus update the new object directly.

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.

How to match a vue-router route and use it in a component function

Using the vue-router package, it is easy to match dynamic segments:
router.map({
'/user/:username': {
component: {
template: '<p>username is {{$route.params.username}}</p>'
}
}
})
However, I couldn't figure out how to use the value a component method, e.g.
router.map({
'/user/:username': {
component: {
ready: function() {
// How to access the param here?
alert($route.params.username);
}
}
}
})
How can I access the matched segment in the component method?
Is almost as what you posted
// inside your component
this.$route.params.username
The key is that in the template you don't need to refer to the this scope, but in methods you have to use it
The $route object is injected in every router maped compeonents , as per your case you would use the params method to get the key/value passed .
router.map({
'/user/:username': {
component: {
ready: function() {
// How to access the param
// use the scope 'this'
alert(this.$route.params.username);
}
}
}
})
for more info check out http://router.vuejs.org/en/api/route-object.html