vue.js Import and load component dynamically - vue.js

I am trying to get the following code dynamically import a component from a folder. However, vue doesn't import anything or show an error. (It's as if it doesn't sense the computed field changing).
What am I doing wrong? (I have already gone through the forums and this seems rare)
<template>
<component v-bind:is="column"></component>
</template>
<script>
export default {
name: "Column",
computed: {
column() {
return () => import(`../columns/${this.$store.state.column}.vue`);
},
}
};
</script>

First you need to import/register all components, you can either do this on component level like below or globally.
export default {
components: {
ColumnA: () => import('../columns/ColumnA'),
ColumnB: () => import('../columns/ColumnB'),
ColumnC: () => import('../columns/ColumnC'),
ColumnD: () => import('../columns/ColumnD'),
}
}
Next you have to make sure you can map your state to column-a to match the component name. if that's the case you can just use:
computed: {
column() {
return this.$store.state.column;
}
}
If not you would have to create a map:
computed: {
column() {
const mappedComponents = {
myStateKeyForColumnA: 'column-a',
myStateKeyForColumnB: 'column-b',
myStateKeyForColumnC: 'column-c',
myStateKeyForColumnD: 'column-d',
}
return mappedComponents[this.$store.state.column];
}
}
Edit
To register the components globally one can use require.context.
in main.js
const context = require.context('./path/to/columns', true, /\.vue$/)
for (const key of context.keys()) {
// key gives us the file name, ie. ./ColumnA.vue
// the code below, to register the component name is based on the above patterh
// likely you will have to modify this
Vue.component(key.slice(2).split('.')[0], () => context(key))
}

Related

Nuxt: Create a plugin that automatically adds a computed to component

I would like to create a Nuxt plugin that automatically adds a computed to components that have a certain property (without using a mixin).
For example, any component that have a addComputedHere property:
export default {
data() {
return {}
},
computed: {
myComputed: () => 'foo'
},
addComputedHere: true
}
would turn into:
export default {
data() {
return {}
},
computed: {
myComputed: () => 'foo',
injectedComputed: () => 'bar' // Injected
},
addComputedHere: true
}
So far, I'm not sure what's the best solution among using a Nuxt plugin/module/middleware or simply a Vue Plugin (if it's feasible).
How would you do it?
If anybody is in the same case, I found a solution by creating a Vue plugin that applies a mixin to customize the component in beforeCreate:
import Vue from 'vue';
const plugin = {
install(Vue, options) {
Vue.mixin({
beforeCreate() {
if (this.$options.addComputedHere) {
this.$options.computed['injectedComputed'] = () => 'bar';
}
}
})
}
};
Vue.use(plugin);

Getting access to varaibles when testing Vue with Jest

I am using the structure below in my Vue.js web application. I am now trying to implement testing to it. But when trying to test the exampleOfFunction it says that this.exampleOfData2 is undefined.
<template>
*Some HTML*
</template>
<script>
*Some Imports*
export default {
data() {
return {
exampleOfData1: [],
exampleOfData2: 100
},
methods: {
exampleOfFunction:function(){
if(this.exampleOfData2 === 100)
{
return false;
}
return true;
},
created() {
},
mounted() {
}
}
</script>
In my testfile I then try to access the code above and I succeed with console.log(FileToTest.data()); I can see the values of data and I can access the function with FileToTest.methods.exampleOfFunction(); but when I call the function it says that this.exampleOfData2 is undefined.
It looks like you're using the component options definition instead of the component instance in your tests.
You should be creating a wrapper by mounting the component, and then you could access the component method via wrapper.vm:
import { shallowMount } from '#vue/test-utils'
import FileToTest from '#/components/FileToTest.vue'
describe('FileToTest', () => {
it('exampleOfFunction returns false by default', () => {
const wrapper = shallowMount(FileToTest)
expect(wrapper.vm.exampleOfFunction()).toBe(false)
})
it('exampleOfFunction returns true when data is not 100', () => {
const wrapper = shallowMount(FileToTest)
wrapper.setData({ exampleOfData2: 0 })
expect(wrapper.vm.exampleOfFunction()).toBe(true)
})
})

Send data from child to parent using VueJS

I'm working on a small project using Django and VueJs. Everything is good, I'd just like to know how I can send data from my component (Modal) to another one after making an Axios request? This is my code:
NB: Please read my code comments to understand what I want to do.
Child : AddContact.vue
methods: {
post(){
getAPI.post('api/contact/post/', this.form).then((response) => {
// here I want to send response data to Contact.vue
}).catch((error) => {
})
}
}
Parent : Contact.vue ( where I want to receive data )
<template>
<addContactModal></addContactModal>
</template>
<script>
import addContactModal from "../modals/contact/addContact.vue";
import { getAPI } from '../../../vue/src/axios-api';
export default {
name: 'Contact',
components: {
addContactModal: addContactModal
}
};
</script>
You can emit a signal from the child component:
getAPI.post('api/contact/post/', this.form).then((response) => {
this.$emit('loaded', response) // emit your signal
}).catch((error) => {
// handle error
})
And then you bind a method of the parent to the signal. Note that:
the name of the signal ($emit's first arg) and the one of the # directive must match (# is short for v-on)
the second argument passed to $emit will become the first argument of the bound function
<template>
<addContactModal #loaded="doSomethingWithResp"></addContactModal>
</template>
...
<script>
import addContactModal from "../modals/contact/addContact.vue";
import { getAPI } from '../../../vue/src/axios-api';
export default {
name: 'Contact',
components: {
addContactModal: addContactModal
},
methods: {
doSomethingWithResp: function (response) {
console.log(response)
}
}
};
</script>

vue.js 2 single file component with dynamic template

I need a single file component to load its template via AJAX.
I search a while for a solution and found some hints about dynamic components.
I crafted a combination of a parent component which imports a child component and renders the child with a dynamic template.
Child component is this:
<template>
<div>placeholder</div>
</template>
<script>
import SomeOtherComponent from './some-other-component.vue';
export default {
name: 'child-component',
components: {
'some-other-component': SomeOtherComponent,
},
};
</script>
Parent component is this
<template>
<component v-if='componentTemplate' :is="dynamicComponent && {template: componentTemplate}"></component>
</template>
<script>
import Axios from 'axios';
import ChildComponent from './child-component.vue';
export default {
name: 'parent-component',
components: {
'child-component': ChildComponent,
},
data() {
return {
dynamicComponent: 'child-component',
componentTemplate: null,
};
},
created() {
const self = this;
this.fetchTemplate().done((htmlCode) => {
self.componentTemplate = htmlCode;
}).fail((error) => {
self.componentTemplate = '<div>error</div>';
});
},
methods: {
fetchTemplate() {
const formLoaded = $.Deferred();
const url = '/get-dynamic-template';
Axios.get(url).then((response) => {
formLoaded.resolve(response.data);
}).catch((error) => {
formLoaded.reject(error);
}).then(() => {
formLoaded.reject();
});
return formLoaded;
},
},
};
</script>
The dynamic template code fetched is this:
<div>
<h1>My dynamic template</h1>
<some-other-component></some-other-component>
</div>
In general the component gets its template as expected and binds to it.
But when there are other components used in this dynamic template (some-other-component) they are not recognized, even if they are correctly registered inside the child component and of course correctly named as 'some-other-component'.
I get this error: [Vue warn]: Unknown custom element: some-other-component - did you register the component correctly? For recursive components, make sure to provide the "name" option.
Do I miss something or is it some kind of issue/bug?
I answer my question myself, because I found an alternative solution after reading a little bit further here https://forum.vuejs.org/t/load-html-code-that-uses-some-vue-js-code-in-it-via-ajax-request/25006/3.
The problem in my code seems to be this logical expression :is="dynamicComponent && {template: componentTemplate}". I found this approach somewhere in the internet.
The original poster propably assumed that this causes the component "dynamicComponent" to be merged with {template: componentTemplate} which should override the template option only, leaving other component options as defined in the imported child-component.vue.
But it seems not to work as expected since && is a boolean operator and not a "object merge" operator. Please somebody prove me wrong, I am not a JavaScript expert after all.
Anyway the following approach works fine:
<template>
<component v-if='componentTemplate' :is="childComponent"></component>
</template>
<script>
import Axios from 'axios';
import SomeOtherComponent from "./some-other-component.vue";
export default {
name: 'parent-component',
components: {
'some-other-component': SomeOtherComponent,
},
data() {
return {
componentTemplate: null,
};
},
computed: {
childComponent() {
return {
template: this.componentTemplate,
components: this.$options.components,
};
},
},
created() {
const self = this;
this.fetchTemplate().done((htmlCode) => {
self.componentTemplate = htmlCode;
}).fail((error) => {
self.componentTemplate = '<div>error</div>';
});
},
methods: {
fetchTemplate() {
const formLoaded = $.Deferred();
const url = '/get-dynamic-template';
Axios.get(url).then((response) => {
formLoaded.resolve(response.data);
}).catch((error) => {
formLoaded.reject(error);
}).then(() => {
formLoaded.reject();
});
return formLoaded;
},
},
};
</script>

How to assign new value to computed in VueJS [duplicate]

I'm trying to create a "settings" component which saves selected values into a store so that all the other components can use those values to change their appearance.
SettingsView.vue:
One of the settings (you can also see it on codepen):
[...]
<p>{{ themeColor }}</p>
<v-radio-group v-model="themeColor">
<v-radio label="light" value="light"></v-radio>
<v-radio label="dark" value="dark"></v-radio>
</v-radio-group>
[...]
<script>
export default {
data () {
return {
// default value
themeColor: 'light',
}
},
computed: {
themeColor () {
return this.$store.state.themeColor
}
},
methods: {
changeThemeColor() {
this.$store.commit('changeThemeColor')
},
}
}
</script>
I don't know how to properly send the selected value of that setting to the store so I just created a mutation with a method (plus the need to have some default value, e.g. themeColor: 'light' like shown above, make it more confusing)
store/modules/Settings.js
const state = {
themeColor: ''
}
const mutations = {
changeThemeColor: state => {
state.themeColor = ''
}
}
export default {
state,
mutations
}
How do I do this properly, so I can then use that value in all the components?
Do I have to use something like getters or actions? I don't really know.
From https://vuex.vuejs.org/en/forms.html, I would use a computed property with getter and setter, ie
export default {
computed: {
themeColor: {
get () {
return this.$store.state.themeColor
},
set (value) {
this.$store.commit('changeThemeColor', value)
}
}
}
}
Note, you do not need data or methods.
Your store should also look more like
const state = {
themeColor: 'light' // default value
}
const mutations = {
changeThemeColor (state, themeColor) {
state.themeColor = themeColor
}
}
Demo ~ https://codepen.io/anon/pen/YYbPww?editors=1011
For instances where you just want to display / read the themeColor state in your component, I recommend using the mapState helper.
import { mapState } from 'vuex'
export default {
// ...
computed: mapState(['themeColor'])
}