Vue - Switching between components - vue.js

Imagine I have a order form with 3 stages - Personal details, Order details, payment.
I want to toggle the next component from within the previous one (hoping to adjust the value of 'step')
<Component1 v-if="step = 1"></Component1>
<Component2 v-else-if="step = 2"></Component2>
<Component3 v-else></Component3>
So, with those on my view, is it possible for me to pass the value of 'step' in to component1 and do something like
<button v-on:click="step = 2">
Submit
</button>
then on that click, update the value of step on my main view (with the 3 components) and with that, hide my first completed component and displaying the second?
Thanks for any insight!

Why you dont use dynamic components?
On your parent component you listen to the event that changes yours step, i named it here nextStep. This event triggers the method changeStep that changes the component name.
<component #nextStep="changeStep" :name="selectedComponent"></component>
import component1 from "../components/component1.vue";
import component2 from "../components/component2.vue";
import component3 from "../components/component3.vue";
export default {
data(){
return {
selectedComponent: "component1"
}
}
},
methods: {
changeStep(step){
this.selectedComponent = step;
}
},
components: {
component1,
component2,
component3
}
//in the child component
method: {
changeStep(){
this.$emit("nextStep", "component2");
}
}
This is how you emit the event to the parent to change the component
You just need to change the property selectedComponent to the component name you want and it will change it

If you are still searching for the answer. This first code to add as your parent component.
<template>
<main>
<component #nextStep="changeStep" v-bind:is="selectedComponent"></component>
</main>
</template>
<script lang="ts">
import Setup1 from '#/components/Setup/Setup1.vue';
import Setup2 from '#/components/Setup/Setup2.vue';
import Setup3 from '#/components/Setup/Setup3.vue';
export default {
name: "SetupView",
components: {
Setup1, Setup2, Setup3
},
data() {
return {
selectedComponent: "Setup1"
}
}, methods: {
changeStep(step: string) {
this.selectedComponent = step;
}
},
}
</script>
Add the code below as the child component .
<template>
<div>
Setup step 1
<button #click="changeStep">Next step</button>
</div>
</template>
<script lang="ts">
export default {
name: "setup1",
methods: {
changeStep() {
this.$emit("nextStep", "Setup2");
}
}
}
</script>
v-bind:is="selectedComponent"

Related

Vue.js: too many $emit from children to parent

I have the main component that containing other components which containing anothers components.
So, I have the click events in these components, but to handle it in my parent component, I need to make $emit call in each nested component.
How to make this process more simple, for example like in React, where I need just pass the function handler into component.
In vue 2.2.3+ you can use provide and inject to pass function from ancestor component to child, like great grandparent to child.
please refer following code
// app.vue
<template>
<div id="app">
<HelloWorld msg="button1" />
<HelloWorld msg="button2" />
<HelloWorld msg="button3" />
count: {{ count }}
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
provide() {
return {
clickHandler: this.clickHandler,
};
},
data() {
return {
count: 0,
};
},
components: {
HelloWorld,
},
methods: {
clickHandler() {
this.count += 1;
console.log("click received");
},
},
};
</script>
// HelloWorld.vue
<template>
<button #click="clickHandler">{{ msg }}</button>
</template>
<script>
export default {
name: "HelloWorld",
inject: ["clickHandler"],
props: {
msg: String,
},
};
</script>
you can see the same clickHandler function from parent is executed with modifying parents count prop on click of each children.
this clickHandler can be injected directly to any descendent at any level therefore application like
parent > child.1 > child.1.1 > child.1.1.1 > child.1.1.1.1(click)
the child.1.1.1.1 can be injected with clickHandler form parent.
try the code at codesandbox
also refer provide/inject
if you need the same value up in the hierarchy or anywhere in the current module, you should try to use the Vuex(State Management) library.

Modify Data From Child To Parent

I am trying to modify a property routing of a parent component from a child as follows:
//- Parent
<template>
<First #toggleContent="routing = !routing" />
</template>
<script>
export default {
data() {
return { routing: true }
}
}
</script>
//- Child (First component)
<template>
<div>
<i class="bx bx-shape-triangle" #click="toggleContent()"></i>
{{routing}}
</div>
</template>
<script>
export default {
props: {
routing: Boolean
},
methods: {
toggleContent() {
this.$emit('toggleContent')
}
}
}
</script>
on the console does not mark me any error.
Your child component requires a prop routing that needs to passed in from parent component, also keep in mind always using kebab case for your events.
<First #toggle-content="routing = !routing" :routing="routing"/>
Modify your event name in emit as well:
toggleContent() {
this.$emit('toggle-content')
}

How to add Vuejs component dynamically to the DOM

I want to add a vue component to the DOM when an option is selected from the drop down list, so it's dynamically displayed
The code that I have tried is as follows
var html = "<component :is=\"dynamickeyvalue\"></component>";
$('#extraOptionsElements').html(html);
is there anyway to load the vue component when it's added as all I am getting now is blank.
I have used the component tag to load the component dynamically. The button click will simulate the component changing feature.
<template>
<button
#click="changeComponent()"
>Click me to change component</button>
<component
:is="theSelectedComponent"
></component>
</template>
<script>
import Component1 from '#/components/Component1'
import Component2 from '#/components/Component2'
import Component3 from '#/components/Component3'
export default {
components: {
Component1,
Component2,
Component3
},
data () {
return {
theSelectedComponent: 'Component1'
}
},
methods: {
changeComponent () {
if (this.theSelectedComponent === 'Component1') {
this.theSelectedComponent = 'Component2'
} else if (this.theSelectedComponent === 'Component2') {
this.theSelectedComponent = 'Component3'
} else {
this.theSelectedComponent = 'Component1'
}
}
}
}
</script>

vue reload child component

I'm using vue, version 2.5.8
I want to reload child component's, or reload parent and then force children components to reload.
I was trying to use this.$forceUpdate() but this is not working.
Do You have any idea how to do this?
Use a :key for the component and reset the key.
See https://michaelnthiessen.com/force-re-render/
Add key to child component, then update the key in parent. Child component will be re-created.
<childComponent :key="childKey"/>
If the children are dynamically created by a v-for or something, you could clear the array and re-assign it, and the children would all be re-created.
To simply have existing components respond to a signal, you want to pass an event bus as a prop, then emit an event to which they will respond. The normal direction of events is up, but it is sometimes appropriate to have them go down.
new Vue({
el: '#app',
data: {
bus: new Vue()
},
components: {
child: {
template: '#child-template',
props: ['bus'],
data() {
return {
value: 0
};
},
methods: {
increment() {
this.value += 1;
},
reset() {
this.value = 0;
}
},
created() {
this.bus.$on('reset', this.reset);
}
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
<div id="app">
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<child :bus="bus">
</child>
<button #click="() => bus.$emit('reset')">Reset</button>
</div>
<template id="child-template">
<div>
{{value}} <button #click="increment">More!</button>
</div>
</template>
I'm using directive v-if which is responsible for conditional rendering. It only affects reloading HTML <template> part. Sections created(), computed are not reloaded. As I understand after framework load components reloading it is not possible. We can only re render a <template>.
Rerender example.
I have a Child.vue component code:
<template>
<div v-if="show">
Child content to render
{{ callDuringReRender() }}
</div>
</template>
<script>
export default {
data() {
return {
show: true
}
}
,
methods: {
showComponent() {
this.show = true
},
hideComponent() {
this.show = false
},
callDuringReRender() {
console.log("function recall during rendering")
}
}
}
</script>
In my Parent.vue component I can call child methods and using it's v-if to force the child rerender
<template>
<div>
<input type="button"
value="ReRender the child"
#click="reRenderChildComponent"/>
<child ref="childComponent"></child>
</div>
</template>
<script>
import Child from './Child.vue'
export default {
methods: {
reRenderChildComponent(){
this.$refs.childComponent.hideComponent();
this.$refs.childComponent.showComponent()
}
},
components: {
Child
}
}
</script>
After clicking a button in console You will notice message "function recall during rendering" informing You that component was rerendered.
This example is from the link that #sureshvv shared
import Vue from 'vue';
Vue.forceUpdate();
// Using the component instance
export default {
methods: {
methodThatForcesUpdate() {
// ...
this.$forceUpdate(); // Notice we have to use a $ here
// ...
}
}
}
I've found that when you want the child component to refresh you either need the passed property to be output in the template somewhere or be accessed by a computed property.
<!-- ParentComponent -->
<template>
<child-component :user="userFromParent"></child-component>
</template>
<!-- ChildComponent -->
<template>
<!-- 'updated' fires if property 'user' is used in the child template -->
<p>user: {{ user.name }}
</template>
<script>
export default {
props: {'user'},
data() { return {}; }
computed: {
// Or use a computed property if only accessing it in the script
getUser() {
return this.user;
}
}
}
</script>

Pass data from one component to all with $emit without using #click in VueJS

Trying to learn vuejs I got to the question how to pass any data from one component to all, using $emit but without using any #click.
It is possible some how that the data to be just available and grab it any time, without using the click?
Let's say we have this example with normal #click and $emit.
main.js
export const eventBus = new Vue()
Hello.vue
<template>
<div>
<h2>This is Hello component</h2>
<button
#click="emitGlobalClickEvent()">Click me</button>
</div>
</template>
<script>
import { eventBus } from '../main'
export default {
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
methods: {
emitGlobalClickEvent () {
eventBus.$emit('messageSelected', this.msg)
}
}
}
</script>
User.vue
<template>
<div>
<h2>This is User component</h2>
<user-one></user-one>
</div>
</template>
<script>
import { eventBus } from '../main'
import UserOne from './UserOne.vue'
export default {
created () {
eventBus.$on('messageSelected', msg => {
console.log(msg)
})
},
components: {
UserOne
}
}
</script>
UserOne.vue
<template>
<div>
<h3>We are in UserOne component</h3>
</div>
</template>
<script>
import { eventBus } from '../main'
export default {
created () {
eventBus.$on('messageSelected', msg => {
console.log('From UserOne message !!!')
})
}
}
</script>
I want to get this message : Welcome to Your Vue.js App from Hello.vue in all components, but without #click, if is possible.
You can create another Javascript file which holds an Object with your initial state. Similar to how you define data in your components.
In this file your export your Object and import it in all Components which need access to this shared state. Something along the lines of this:
import Store from 'store';
data() {
return {
store
}
}
This might help:
https://v2.vuejs.org/v2/guide/state-management.html
At this point if you app grows even more in complexity you might also start checking out Vuex which helps to keep track of changes(mutations) inside of your store.
The given example is essential a very oversimplified version of Vuex.