I am coming from an angular mindset and now I am trying to learn vue.js. I am using webpack and I have the following three .vue classes.
CounterDisplay.vue, IncrementButton.vue, andApp.vue. I want to increment thecountvariable but all it does isconsole.loghow many times I pressed the button. I am trying to figure out how child to parent and parent to child work in vue. Then I need to figure out the correct pattern to use vue in a very large application. In angular you have amoduleand in there you put your components and services etc. How doesvue` do this?
CounterDisplay.vue
<template>
<div id="#counterDisplay">
{{count}}
</div>
</template>
<script>
export default {
data () {
return {
count: 0
}
}
}
</script>
<style scoped>
</style>
IncrementButton.vue
<template>
<button #click.prevent="active">+1</button>
</template>
<script>
export default {
methods: {
active () {
console.log('+1 Pressed')
}
}
}
</script>
<style scoped></style>
App.vue
<template>
<div id="app">
<h3>Increment:</h3>
<increment></increment>
<h3>Counter:</h3>
<counter></counter>
</div>
</template>
<script>
import Counter from './components/CounterDisplay.vue'
import Increment from './components/IncrementButton.vue'
export default {
components: {
Counter,
Increment
}
}
</script>
<style>
</style>
This is the output:
As you said:
. I am trying to figure out how child to parent and parent to child work
This is how you do it:
Set up a counter data property in App.vue
Emit an event using vm.$emit() in IncrementButton.vue component on every button click
Set up an event listener on this component increment and execute the method whenever this event is emitted
In the event call back method increase the counter by 1
Send the counter property as a prop to **CounterDisplay.vue
App.vue*
<template>
<div id="app">
<h3>Increment:</h3>
<increment #btn-clicked="increaseCounter"></increment>
<h3>Counter:</h3>
<counter :counter="counter"></counter>
</div>
</template>
<script>
import Counter from './components/CounterDisplay.vue'
import Increment from './components/IncrementButton.vue'
export default {
data(){
counter:0
},
components: {
Counter,
Increment
},
methods:{
increaseCounter(){
this.counter ++;
}
}
}
</script>
<style>
</style>
IncrementButton.vue
<template>
<button #click.prevent="active">+1</button>
</template>
<script>
import {EventBus} from './path/to/main.js'
export default {
methods: {
active () {
console.log('+1 Pressed')
//emitting an event
this.$emit('btn-clicked');
}
}
}
</script>
<style scoped></style>
CounterDisplay.vue
<template>
<div id="#counterDisplay">
{{counter}}
</div>
</template>
<script>
export default {
props:['counter'],
data () {
return {
}
},
}
</script>
<style scoped>
</style>
--------------------
Since the two components are non parent-child components the simple scenario would be using an EventBus
Declare an EventBus which is nothing more than an empty Vue instance in your main.js file
export const EventBus = new Vue();
The only focus of this empty vue instance is to listen and respond to events from components
IncrementButton.vue
<template>
<button #click.prevent="active">+1</button>
</template>
<script>
import {EventBus} from './path/to/main.js'
export default {
methods: {
active () {
console.log('+1 Pressed')
//emitting an event
//Syntax is EventBus.$emit('event-name', eventData);
EventBus.$emit('btn-clicked', 1);
}
}
}
</script>
<style scoped></style>
Now setup an listener for btn-clicked event in created hook of 8*CounterDisplay.vue**
<template>
<div id="#counterDisplay">
{{count}}
</div>
</template>
<script>
import {EventBus} from './path/to/main.js'
export default {
data () {
return {
count: 0
}
},
created(){
EventBus.$on('btn-clicked', (eventData) => {
this.count = this.count + eventData;
});
}
}
</script>
<style scoped>
</style>
Note: As you want to know the correct pattern for a large app, I would recommend using vuex
Related
I can't seem to pass dynamically modified properties from layouts into the <Nuxt /> component.
This is my ~/layouts/default.vue
<template>
<div>
<input v-model="myprop" />
<span>{{myprop}}</span>
<Nuxt />
</div>
</template>
<script>
export default {
provide: function () {
return {myprop: this.myprop};
},
data: () => ({
myprop: 'hello galaxy',
}),
}
</script>
This is my ~/pages/index.vue
<template>
<div>My Prop is: {{myprop}}</div>
</template>
<script>
export default {
inject: ["myprop"]
}
</script>
On the web page I see hello galaxy printed 3 times, once in the input, once in a span, and once in the Nuxt component. But when I edit the input field, only the span is updated. The Nuxt component does not capture the changes in myprop. The Nuxt component continues to show only hello galaxy while put the input and span shows changes as I type on my keyboard
What am I doing wrong?
The provide/inject is useful for simple situation, but if you've some reactive stuff the vuex store is more convenient :
in store/index.js
add a state called search and its mutations and actions :
export const state=()=>({
search:''
})
export const mutations ={
SET_SEARCH(state,payload){
state.search=payload
}
}
export const actions ={
setSearch(context,payload){
context.commit('SET_SEARCH',payload)
}
}
in layout/default.vue add computed property with setter/getter bound to the store:
<template>
<div>
<input v-model="search" />
<span>{{search}}</span>
<Nuxt />
</div>
</template>
<script>
export default {
computed:{
search:{
get(){
return this.$store.state.search
},
set(val){
this.$store.dispatch('setSearch',val)
}
}
}
}
</script>
in pages/index.vue :
<template>
<div>My search is: {{search}}</div>
</template>
<script>
export default {
computed:{
search(){
return this.$store.state.search
}
}
}
</script>
how do you affect multiple (in this case just 2) children components owned by two different parent components when an action is triggered by one of the children components?
For example I have a component, lets call it <component-one/>. Inside this component I have something like below:
<div #mouseover="hover=true" #mouseleave="hover=false" :class="setColour">
<div class="icon-wrapper commercial-layout position-relative">
<u-button icon color="transparent" #click="toggleCommercials">
<u-icon :icon="icon" color="white"/>
</u-button>
<small class="commercial-ind">COMMERCIAL ADS</small>
<div class="commercial-layout commercial-ind">{{hide}}</div>
</div>
</div>
computed: {
setColour () {
if (this.hover) {
return 'bg-danger'
}
else if (this.commercials) {
return 'bg-primary'
}
else if (!this.commercials) {
return 'bg-secondary'
}
},
watch: {
setColour: function(val) {
console.log("val",val)
}
}
But somewhere else in the code base I have two other components, lets call them <component-two/> and <component-three/>. Inside those components I use component-one. When I push on the button from component-two I want the same effect to also be triggered in component-three, and vice versa, but I'm not quite sure how to achieve that.
Currently both component-two and component-three just have component-one. I've tried adding a watch in component-one but it doesn't really do anything other than capturing changes to the setColour computed property. (I naively thought by capturing the change, all places where component-one is used will get updated)
I'm not sure I totally understand your specific component relationships, but in general I recommend using Vuex.
Using Vue 2 and the CLI, I created sample SFCs that use Vuex to store the background color CSS style. Each child is associated with a specific color, and clicking it's button updates the color of all sibling components.
/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
commonBgColor: 'navajowhite'
},
mutations: {
updateBgColor(state, newColor) {
state.commonBgColor = newColor;
}
}
})
Parent.vue
<template>
<div class="parent">
<child initBgColor="aquamarine" instanceName="One" />
<child initBgColor="mediumorchid" instanceName="Two" />
</div>
</template>
<script>
import Child from './Child.vue'
export default {
components: {
Child
}
}
</script>
Child.vue
<template>
<div class="child">
<div class="row">
<div class="col-md-6" :style="currentBgColor">
<span>Sibling Component {{ instanceName }}</span>
<button type="button" class="btn btn-secondary" #click="updateCommonBgColor">Change All Sibling Colors</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
initBgColor: {
type: String,
required: true
},
instanceName: {
type: String,
required: true
}
},
data() {
return {
backgroundColor: this.initBgColor
}
},
computed: {
currentBgColor() {
return 'background-color: ' + this.$store.state.commonBgColor;
}
},
methods: {
updateCommonBgColor() {
this.$store.commit('updateBgColor', this.backgroundColor);
}
}
}
</script>
<style scoped>
.child {
margin-top: 0.5rem;
}
span {
font-size: 1.5rem;
padding: 0.5rem;
}
button {
float: right;
}
</style>
There is my default layout.
I want to change visibility on my header, when my modal is open, but I don't know how to change it dynamically. Any ideas?
// default.vue
<template>
<div class="container">
<header class="default-header">
<router-link class="logo" to="/"></router-link>
<div class="button-group">
<router-link to="/login" class="btn">Log in</router-link>
<router-link to="/register" class="btn">Sign up</router-link>
</div>
</header>
<nuxt />
</div>
</template>
//index.vue
<template>
<div>
<div #click="openModal">Open Modal</div>
<modal-popup v-model="showModal"></modal-popup>
</div>
</template>
<script>
import ModalPopup from "~/components/ModalPopup";
export default {
name: "Login",
components: {
ModalPopup
},
data() {
return {
showModal: false;
}
},
methods() {
openModal() {
this.showModal = true;
},
closeModal() {
this.showModal = false;
},
}
};
</script>
To do it without a store you can create an EventBus to trigger events which you can listen to. An eventbus provides communication between vue components.
You can create an eventbus js file.
import Vue from 'vue';
const EventBus = new Vue();
export default EventBus;
Then in your components you can import it
import EventBus from '/eventbus'
In the index.vue component in your openModal() function you can change it to trigger the open modal event like this
openModal() {
EventBus.$emit('modal-opened', true);
this.showModal = true;
}
then in your default.vue component you can add a listener in the mounted function
mounted() {
EventBus.$on(‘modal-open’, function (payLoad) {
// Change header visibility here
});
}
Extra Note
If you don't want to import the Eventbus all the time you can import Eventbus in your app.js and just before your new Vue() you can add bus to the vue properties so your file will look something like this
import Vue from 'vue'
import EventBus from '/eventbus'
Object.defineProperties(
Vue.prototype,
{
$bus: {
get() => { return EventBus; }
}
}
)
new Vue({})
Then you can access the bus in your components like this
this.$bus.$emit('modal-open', true);
and
this.$bus.$on('modal-open', function(payload) {
})
Hope that helps
I'm very new to Vue and am trying to get a simple scoped breakpoint within my component.
<template>
<div class="date-picker">
<p>{{ date }}</p>
</div>
</template>
<script>
import moment from 'moment'
export default {
data () {
debugger;
return {
date: moment().format('YYYY-MM-DD')
}
}
}
</script>
<style lang="scss" scoped>
</style>
I was hoping the debugger statement could put a breakpoint where I could access moment in the Chrome developer console but that doesn't appear to work. Is this possible?
you cannot put a debugger in the data property.
try something like this:
<template>
<div class="date-picker">
<p>{{ date }}</p>
<button #click="debuggerButton">Debugger</button>
</div>
</template>
<script>
import moment from 'moment'
export default {
data () {
return {
date: moment().format('YYYY-MM-DD')
}
},
methods: {
debuggerButton(){
debugger
}
}
}
</script>
I config a global check user if LoggedIn or not to rendering components in NuxtJS but I cant do it. Please help me.
I write computed to wrapper components (layouts/default.vue)
~/layouts/default.vue
<template>
<router-view></router-view>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
computed: {
...mapGetters({
LoggedIn: 'authUser'
})
}
}
}
</script>
But I cant use it on children components / pages.
~/pages/index.vue
<template>
<div class="index-page">
<div class="" v-if="LoggedIn">
asdasd
</div>
</div>
</template>
<script>
export default {
mounted() {
console.log(this.LoggedIn)
// Result: Undefined
}
}
</script>