Component Communication in Element UI Using the Emitter Mixin: Dispatch and Broadcast Explained

The Element UI emitter mixin enables Vue components to communicate bidirectionally through the component tree using dispatch for upward propagation to ancestors and broadcast for downward propagation to descendants, eliminating the need for a global event bus.

The ElemeFE/element repository implements a lightweight communication layer for Vue components through the emitter mixin, which provides targeted event propagation without relying on a global event bus. This pattern allows child components to notify specific ancestors and parent components to signal specific descendants using the componentName convention. Understanding this mechanism is essential for debugging form validation, menu interactions, and other complex component relationships in Element UI.

How the Emitter Mixin Enables Tree-Based Communication

The emitter mixin lives in src/mixins/emitter.js and adds two instance methods to Vue components. Both methods rely on a convention: each component that participates in communication defines a static componentName option (for example, name: 'ElFormItem').

dispatch(componentName, eventName, params) travels upward from a child to its nearest matching ancestor by walking the $parent chain. When it finds a component whose componentName matches the target, it calls $emit on that ancestor.

broadcast(componentName, eventName, params) travels downward from a parent to all matching descendants by recursively traversing $children. When a descendant matches the componentName, the mixin emits the event on that component and continues recursion for non-matching branches.

Core Implementation in src/mixins/emitter.js

The implementation in src/mixins/emitter.js consists of a mixin object containing the public methods and an internal recursive helper for broadcast.

The dispatch method (lines 14–27) climbs the component tree until it locates the requested ancestor:

// src/mixins/emitter.js
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) name = parent.$options.componentName;
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },

    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

The broadcast helper function (lines 1–11) handles downward propagation by iterating through $children and recursing when names do not match:

function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.componentName;
    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}

Real-World Usage Examples

Element UI uses the emitter mixin extensively for form validation, menu state management, and tree operations.

Form Validation: ElInput Dispatching to ElFormItem

In packages/input/src/input.vue, input components notify their parent form items about value changes to trigger validation. The ElInput component mixes in the emitter and calls dispatch to reach the nearest ElFormItem:

// packages/input/src/input.vue
export default {
  name: 'ElInput',
  mixins: [Emitter],
  methods: {
    handleInput(val) {
      this.dispatch('ElFormItem', 'el.form.change', [val]);
    }
  }
};

This call climbs the $parent chain until it finds an ancestor with componentName: 'ElFormItem', then emits el.form.change on that instance.

In packages/menu/src/menu.vue, the root menu component broadcasts collapse state changes to all submenu descendants. The ElMenu component uses broadcast to reach every ElSubmenu instance simultaneously:

// packages/menu/src/menu.vue
export default {
  name: 'ElMenu',
  mixins: [Emitter],
  methods: {
    toggleAllSubs(value) {
      this.broadcast('ElSubmenu', 'toggle-collapse', value);
    }
  }
};

This recursion traverses the entire subtree, emitting toggle-collapse on each descendant named ElSubmenu regardless of nesting depth.

Setting Up Event Listeners

Components that receive emitter events register listeners using Vue's $on method, typically in the mounted lifecycle hook. The event name must match the string passed to dispatch or broadcast.

For example, ElFormItem listens for validation triggers from child inputs:

export default {
  name: 'ElFormItem',
  mounted() {
    this.$on('el.form.change', this.handleChange);
    this.$on('el.form.blur', this.handleBlur);
  },
  methods: {
    handleChange(val) {
      this.validate('change');
    }
  }
};

This pattern creates a direct communication channel between specific component types without polluting the global event namespace.

Summary

  • The emitter mixin in src/mixins/emitter.js provides dispatch for upward communication and broadcast for downward communication within the Vue component tree.
  • Both methods rely on the componentName option to locate target components, not the standard name property.
  • dispatch walks the $parent chain to find the nearest matching ancestor, while broadcast recursively traverses $children to find all matching descendants.
  • Element UI uses this pattern for critical features like form validation (ElInput to ElFormItem) and menu state (ElMenu to ElSubmenu).
  • Listeners register via $on using the exact event name passed to the emitter methods.

Frequently Asked Questions

What is the difference between dispatch and broadcast in the Element UI emitter mixin?

dispatch sends events upward through the component tree to the nearest ancestor matching the specified componentName, while broadcast sends events downward to all descendants matching that name. Dispatch stops at the first match, making it ideal for parent-child pairs like form inputs and form items. Broadcast reaches every matching descendant at any depth, suitable for global state updates like menu collapse.

How does the emitter mixin identify the correct target component?

The mixin uses the componentName option defined in a component's $options, not the standard name property. When calling dispatch('ElFormItem', ...), the mixin checks parent.$options.componentName === 'ElFormItem' while traversing the tree. Components must explicitly set this option (for example, name: 'ElFormItem' in the component definition) to participate in emitter communication.

Where is the emitter mixin defined in the Element UI codebase?

The core logic resides in src/mixins/emitter.js, which exports a mixin object containing the dispatch and broadcast methods. Components throughout the library—such as those in packages/input/src/input.vue and packages/menu/src/menu.vue—import and apply this mixin to enable tree-based communication.

Why does Element UI use the emitter mixin instead of a global event bus?

The emitter mixin provides scoped communication that travels only through relevant component subtrees, preventing accidental cross-talk between unrelated component instances. By using explicit componentName targeting, the pattern maintains clear relationships between communicators and avoids the maintenance overhead and memory leak risks associated with global event buses in large component hierarchies.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →