I've got a single page app and im trying to import and render components based on what components exists in array(blockCount). Im storing several strings(component name) in said array.
Vue:
computed: {
componentInstance () {
for(var i = 0; i < this.blockCount.length; i++){
return () => import(`#/components/${this.blockCount[i]}`)
}
}
},
Html:
<component v-for="component in blockCount" :is="componentInstance" />
So the issue im having is that the function stop at item[0] and just iterates that item. And im not quite sure how to iterate this function in a dynamic way.
You might want to define a child component
ChildComponent.vue
<template>
<component :is="componentInstance" />
</template>
<script>
export default {
props: ['componentName'],
computed: {
componentInstance() {
return () => import(`#/components/${this.componentName}`)
}
}
}
</script>
and render in parent:
<child-component v-for="(component, index) in blockCount" :key="index" :componentName="component"/>
Related
This is my approach of calling a method after v-for has done looping. It works perfectly fine but i still want to know if there's a better approach than this or if vue is offering something like v-for callback.
Parent Component
<template>
<loader-component v-show="show_loader" />
<list-component #onRendered="hideLoader"/>
</template>
<script>
export default {
components: {
loaderComponent,
listComponent
},
data() {
return {
show_loader: true
}
}
methods: {
hideLoader() {
this.show_loader = false;
}
}
}
</script>
List Component
<template>
<item-component v-for="(item, key) in items" :isRendered="isRendered(key)" />
</template>
<script>
export default {
prop: ['items'],
methods: {
isRendered(key) {
let total_items = this.items.length - 1;
if (key === total_items) {
this.$emit('onRendered');
}
}
}
}
</script>
I believe there is a misunderstanding in how exactly Vue updates its reactive properties.
I made a little demo for you hoping it clears it up.
https://codesandbox.io/s/prod-star-pzjhc
I would also recommend some reading from the Vue docs computed properties
For a hangman game I have a Word.vue component in which I'm trying to initialise an array named wordToGuessAsArray containing n empty items (n = number of letters in word to guess):
<template>
<section>
<div v-for="(letter, i) in wordToGuessAsArray" :key="i">
</div>
</section>
</template>
<script>
export default {
computed: {
wordToGuessAsArray () {
return this.$store.state.wordToGuessAsArray
}
},
mounted () {
const wordToGuess = 'strawberry'
for (var i = 0; i < wordToGuess.length; i++) {
this.$store.commit('SET_WORD_AS_ARRAY_PUSH')
}
}
}
</script>
Below is the content of my store relevant to this question:
state: {
wordToGuessAsArray: [],
},
mutations: {
SET_WORD_AS_ARRAY_PUSH (state) {
state.wordToGuessAsArray.push('')
},
SET_WORD_AS_ARRAY (state, value) {
state.wordToGuessAsArray[value.i] = value.letter
}
}
My problem is the following. In my Keyboard.vue component, when user picks a letter that does indeed belong to the word to guess, I update my state thus:
this.$store.commit('SET_WORD_AS_ARRAY', { letter, i })
I expect this mutation to update the word in my Word.vue component here:
<div v-for="(letter, i) in wordToGuessAsArray" :key="i">
Yet it doesn't. wordToGuessAsArray seems non reactive, why?
It is because state.wordToGuessAsArray[value.i] = value.letter is not reactive.
Because Vue.js only watch array methods like push or splice.
You need to do this.$set(state.wordToGuessAsArray, value.i, value.letter);
Or in Vuex :
Vue.set(state.wordToGuessAsArray, value.i, value.letter); and import Vue in your file.
Read more here :
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Vuejs and Vue.set(), update array
I have some global-registered base components to be listed & rendered on the UI as the user drag and drop it on the layout editor. Then, I want it to be processed on a <ComponentRenderer/> component.
Inside of <ComponentRenderer/>, I currently have this kind of logic:
<template>
<div class="component-renderer">
<radio-button
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'radio'"
></radio-button>
<check-box
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'checkbox'"
></check-box>
<standard-input
:pageId="pageId"
:input="input"
:preview="preview"
v-if="input.type == 'input'"
></standard-input>
...
...
</div>
</template>
Now instead of hard-coding & comparing it manually using v-if, I want it to dynamically compare itself and render it's element and properties so that I don't need to register the other one when a new component was added. Something that looks like this:
<template>
<div class="component-renderer" v-html="preRenderComponent"></div>
</template>
<script>
export default {
props: {
targetedComponent: {
type: Object,
required: true
}
},
computed: {
preRenderComponent() {
return this.$options.components.filter(
component =>
component.extendOptions.name.toLowerCase() == "base" + this.targetedComponent.type.toLowerCase() // E.g: 'input'
);
}
}
};
</script>
Is it possible? And if possible, how could I render the element and properties? Knowing that when I do console.log(Vue.options.components) and exploring it, it does not provide the element that gonna be rendered.
First thing first, I would say thanks for Steven Spungin & Phil for giving an information about this issue.
I tried to implements it on my code and it does work as I expected. For anyone who wondering what is it looks like, here I provides the code that have been implemented.
LayoutRenderer.vue :
<template>
<GridLayout
v-if="pageComponents.length != 0"
:layout.sync="pageComponents"
:col-num="12"
:row-height="30"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:vertical-compact="true"
:margin="[10, 10]"
:use-css-transforms="true"
:responsive="true"
:auto-size="true"
>
<GridItem
v-for="component in pageComponents"
:key="component.i"
:x="component.x"
:y="component.y"
:w="component.w"
:h="component.h"
:i="component.i"
>
<!-- <ComponentRenderer :component="component"></ComponentRenderer> -->
<Component :is="named(component)"></Component>
</GridItem>
</GridLayout>
</template>
<script>
import VueGridLayout from "vue-grid-layout";
export default {
components: {
GridLayout: VueGridLayout.GridLayout,
GridItem: VueGridLayout.GridItem
},
data() {
return {
pageComponents: []
};
},
created() {
this.fetchComponents();
},
methods: {
fetchComponents() {
let pageId = this.$route.params.component.pageId;
this.$store.dispatch("components/fetchComponents", pageId).then(() => {
this.pageComponents = this.$store.getters["components/components"];
});
},
named(component) {
let componentName = this.$options.filters.capitalize(component.type);
return `Base${componentName}`;
}
}
};
</script>
I have a component that dynamic render components that get as props
<template>
<div>
<component :is="component" :data="data" v-if="component" />
</div>
</template>
<script>
export default {
name: 'dynamic-component-renderer',
props: ['data', 'type'],
computed: {
component() {
if (!this.type) {
return null;
}
return this.type;
},
}
}
</script>
The issue is in imports, I need to dynamic import, I know that I can do dynamic importing with webpack like this: () => import('./my-async-component'), but in my case, I don't need lazyLoad.
So I need to have a generic dummy component (dynamic-component-renderer) that will not know what components will get and dynamically render.
I am building a dashboard app where users will be able to choose widgets that appear in certain positions. I am doing this using
<component :is="name-of-component">
Its all working well but I want the user to be able to edit the component and emit the changes to the parent. By editing lets say for example 1 component displays a welcome message which the user can change.
Right now I have the following
Dashboard.vue
<template>
<component :is="name-of-component"></component>
</template>
<script>
data () {
return {
name-of-component: 'selected-component-name'
}
}
</script>
In the script is the computed, mounted etc. which I don't think have relevance to the question.
Since Im using component :is Im finding it tricky to pass props and emit changes. In my store I have 2 props for the component (title & subtitle) in an array. I can hardcode :props="welcomeMessage" but I don't want to hard code since Im using :is and position of widgets can change.
Emit is also causing an issue for me. I can, of course, emit by hard coding the call to the component but since Im using :is its not going to work for me.
Heres what is working but I need to make it dynamic as any component :is wan contain any widget. Any ideas?
<component
:is="welcomeMessage"
:props="dashboard.welcomeMessage"
#update-welcomeMessage="welcomeMessage(e)">
</component>
OR
<component
:is="busStops"
:props="dashboard.myBusStop"
#update-busStop="busStop(e)">
</component>
Id like to have the components so that I can pull in the different concerns and have each one be more like this where "name-of-component" could be used to populate the :is, :props and #update:
<component
:is="name-of-component"
:props="dashboard.name-of-component"
#update-name-of-component="name-of-component(e)">
</component>
You can use the v-bind and v-on capabilities, and use computed properties just like you are already doing it. I'll explain myself:
<some-component #some-event="doThis" foo="bar"></some-component>
is the same as writing:
<some-component v-bind="{foo: 'bar'}" v-on="{'some-event': doThis}"></some-component>
That means that you can write computed properties to compute which listeners and attributes you want to use for your dynamic component.
I wrote a complete example on jsFiddle if you want: https://jsfiddle.net/tsc2pmrx/
Template:
<div id="app">
<component :is="componentToUse" v-on="listeners" v-bind="attributes"></component>
</div>
JS:
Vue.component('greeting', {
props: ['name'],
template: '<h1>Welcome {{ name }} !</h1>',
mounted () {
setTimeout(() => {
this.$emit('some-event')
}, 2000)
}
});
Vue.component('other-component', {
template: '<h1>Welcome to Other Component</h1>'
})
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data: {
componentToUse: 'greeting'
},
methods: {
handleOtherComponentEvent () {
console.log('Hello World from other component listener')
},
handleGreetingComponentEvent () {
console.log('Hello World from greeting component event listener')
}
},
computed: {
listeners () {
if (this.componentToUse === 'greeting') {
return {
'some-event': this.handleOtherComponentEvent
}
} else if (this.componentToUse === 'other-component') {
return {
'some-greeting-event': this.handleGreetingComponentEvent
}
}
return {}
},
attributes () {
if (this.componentToUse === 'greeting') {
return {
'name': 'Hammerbot'
}
}
return {}
}
}
});