Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Component Communication Patterns in Vue.js

Tech 1

Props and Events: Parent-Child Communication

The fundamental approach for parent-child data flow involves passing data downward through properties and emitting events upward.

<!-- ProductDisplay.vue -->
<template>
  <section>
    <ItemCard :product="currentProduct" @add-to-cart="onAddItem" />
  </section>
</template>

<script>
import ItemCard from './ItemCard.vue';

export default {
  data() {
    return {
      currentProduct: {
        id: 1,
        name: 'Wireless Headphones',
        price: 79.99
      }
    };
  },
  components: { ItemCard },
  methods: {
    onAddItem(productId) {
      console.log('Item added:', productId);
    }
  }
};
</script>
<!-- ItemCard.vue -->
<template>
  <article>
    <h3>{{ product.name }}</h3>
    <span class="price">${{ product.price }}</span>
    <button @click="addToCart">Add to Cart</button>
  </article>
</template>

<script>
export default {
  props: {
    product: {
      type: Object,
      required: true
    }
  },
  methods: {
    addToCart() {
      this.$emit('add-to-cart', this.product.id);
    }
  }
};
</script>

Sibling Component Communication via Event Bus

Components without direct parent-child relationships can communicate through a shared event bus instance.

// eventHub.js
import Vue from 'vue';
export const eventHub = new Vue();
<!-- NotificationSender.vue -->
<template>
  <div class="sender">
    <input v-model="notificationText" placeholder="Type a notification" />
    <button @click="broadcastNotification">Publish</button>
  </div>
</template>

<script>
import { eventHub } from '../eventHub.js';

export default {
  data() {
    return {
      notificationText: ''
    };
  },
  methods: {
    broadcastNotification() {
      eventHub.$emit('notification', this.notificationText);
      this.notificationText = '';
    }
  }
};
</script>
<!-- NotificationReceiver.vue -->
<template>
  <div class="receiver">
    <strong>Latest Notification:</strong>
    <p v-if="latestNotification">{{ latestNotification }}</p>
    <p v-else class="empty">No notifications yet</p>
  </div>
</template>

<script>
import { eventHub } from '../eventHub.js';

export default {
  data() {
    return {
      latestNotification: ''
    };
  },
  mounted() {
    eventHub.$on('notification', (text) => {
      this.latestNotification = text;
    });
  },
  beforeUnmount() {
    eventHub.$off('notification');
  }
};
</script>

Ancestor-Descendant Communication with provide/inject

For multi-level component trees, providing and injecting dependencies avoids prop drilling through intermediate components.

<!-- AppRoot.vue -->
<template>
  <div id="app">
    <NavigationBar />
    <router-view />
  </div>
</template>

<script>
import { provide } from 'vue';
import NavigationBar from './NavigationBar.vue';

export default {
  components: { NavigationBar },
  setup() {
    const themeConfig = {
      primaryColor: '#3498db',
      darkMode: false,
      fontSize: 16
    };

    provide('theme', themeConfig);
  }
};
</script>
<!-- ThemeToggle.vue -->
<template>
  <div class="theme-toggle">
    <button @click="toggleDarkMode">
      {{ currentTheme.darkMode ? 'Light Mode' : 'Dark Mode' }}
    </button>
  </div>
</template>

<script>
import { inject, computed } from 'vue';

export default {
  setup() {
    const theme = inject('theme');

    const currentTheme = computed(() => theme);

    const toggleDarkMode = () => {
      theme.darkMode = !theme.darkMode;
    };

    return { currentTheme, toggleDarkMode };
  }
};
</script>

Centralized State Management with Vuex

For complex applications with numerous interconnected components, Vuex provides a structured approach to managing shared applicatoin state.

// src/store/index.js
import { createStore } from 'vuex';

export default createStore({
  state() {
    return {
      userSettings: {
        username: '',
        notifications: true,
        language: 'en'
      },
      isAuthenticated: false
    };
  },
  mutations: {
    updateUsername(state, newName) {
      state.userSettings.username = newName;
    },
    toggleNotifications(state) {
      state.userSettings.notifications = !state.userSettings.notifications;
    },
    setAuthStatus(state, status) {
      state.isAuthenticated = status;
    }
  },
  actions: {
    async login({ commit }, credentials) {
      const response = await api.authenticate(credentials);
      commit('setAuthStatus', true);
      commit('updateUsername', response.username);
    }
  },
  getters: {
    currentUsername: (state) => state.userSettings.username,
    hasUnreadNotifications: (state) => state.userSettings.notifications
  }
});
<!-- UserProfile.vue -->
<template>
  <div class="profile">
    <input v-model="localUsername" @blur="saveUsername" />
    <span>Welcome, {{ username }}</span>
  </div>
</template>

<script>
import { ref } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();
    const localUsername = ref('');

    const username = () => store.getters.currentUsername;

    const saveUsername = () => {
      store.commit('updateUsername', localUsername.value);
    };

    return { localUsername, username, saveUsername };
  }
};
</script>
<!-- SettingsPanel.vue -->
<template>
  <div class="settings">
    <label>
      <input type="checkbox" v-model="notificationsEnabled" />
      Enable Notifications
    </label>
  </div>
</template>

<script>
import { ref, watch } from 'vue';
import { useStore } from 'vuex';

export default {
  setup() {
    const store = useStore();
    const notificationsEnabled = ref(false);

    watch(notificationsEnabled, (newValue) => {
      store.commit('toggleNotifications');
    });

    return { notificationsEnabled };
  }
};
</script>

Direct Component Access via $refs

When you need to invoke methods or access internal state of child components directly, refs provide a reference to the component instance.

<!-- MediaPlayer.vue -->
<template>
  <div class="player">
    <VideoElement ref="videoPlayer" />
    <div class="controls">
      <button @click="play">Play</button>
      <button @click="pause">Pause</button>
      <button @click="restart">Restart</button>
    </div>
  </div>
</template>

<script>
import VideoElement from './VideoElement.vue';

export default {
  components: { VideoElement },
  methods: {
    play() {
      this.$refs.videoPlayer.play();
    },
    pause() {
      this.$refs.videoPlayer.pause();
    },
    restart() {
      this.$refs.videoPlayer.reset();
    }
  }
};
</script>
<!-- VideoElement.vue -->
<template>
  <div class="video-wrapper">
    <video :src="videoSource" ref="videoTag"></video>
    <p>Current Time: {{ currentTime }}s</p>
    <p>Status: {{ playStatus }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      videoSource: '/assets/sample-video.mp4',
      currentTime: 0
    };
  },
  computed: {
    playStatus() {
      return this.$refs.videoTag?.paused ? 'Paused' : 'Playing';
    }
  },
  methods: {
    play() {
      this.$refs.videoTag.play();
    },
    pause() {
      this.$refs.videoTag.pause();
    },
    reset() {
      this.$refs.videoTag.currentTime = 0;
      this.$refs.videoTag.play();
    }
  }
};
</script>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.