Master Vue 3 Form Components: Build, Validate, and Communicate Efficiently

This article explains how to create a robust Vue 3 form component using Element Plus, covering component hierarchy, data binding with model, rule definition with async-validator, provide/inject communication, and step‑by‑step validation logic with practical code examples.

JavaEdge
JavaEdge
JavaEdge
Master Vue 3 Form Components: Build, Validate, and Communicate Efficiently

Preface

The previous article introduced Jest testing for component libraries; now we focus on a form component that not only renders UI but also handles rich interactions, starting from Element Plus form components.

Form Component Overview

Element Plus provides several form‑related components such as el-form (the outer container), el-form-item (label and validation management), and input widgets like el-input or el-switch. The typical template consists of three layers: el-form – the form container. el-form-item – manages each field’s label and validation. el-input / el-switch – the actual input controls.

<el-form :model="ruleForm" :rules="rules" ref="form" label-width="100px">
  <el-form-item label="Activity Name" prop="name">
    <el-input v-model="ruleForm.name"></el-input>
  </el-form-item>
  ...
</el-form>

A simplified version that only keeps el-input demonstrates the basic data flow: el-form uses :model for data binding and :rules for validation. el-form-item wraps the input, performs validation, and displays error messages.

<el-form :model="ruleForm" :rules="rules" ref="form">
  <el-form-item label="Username" prop="username">
    <el-input v-model="ruleForm.username"></el-input>
  </el-form-item>
  <el-form-item label="Password" prop="passwd">
    <el-input type="textarea" v-model="ruleForm.passwd"></el-input>
  </el-form-item>
  <el-form-item>
    <el-button type="primary" @click="submitForm()">Login</el-button>
  </el-form-item>
</el-form>

Rules and Model Workflow

We create a reactive ruleForm object for user input and a reactive rules object that follows the async-validator schema. Validation is triggered via the form’s validate method, which displays error messages when a rule fails.

const ruleForm = reactive<UserForm>({
  username: "",
  passwd: ""
});

// Define validation rules
const rules = reactive({
  rules: {
    username: { required: true, min: 1, max: 20, message: 'Length 1‑20', trigger: 'blur' },
    passwd: [{ required: true, message: 'Password required', trigger: 'blur' }]
  }
});

function submitForm() {
  form.value.validate(valid => {
    if (valid) alert('submit!');
    else console.log('error submit!!');
  });
}
Form validation flow diagram
Form validation flow diagram

Implementing the Form Component

In src/components/Form.vue we register props ( label, prop) and expose a validate method so the parent form can trigger validation on each item.

interface Props {
  label?: string;
  prop?: string;
}

const props = withDefaults(defineProps<Props>(), { label: "", prop: "" });

const formData = inject(key);
const o: FormItem = { validate };

defineExpose(o);

Each FormItem.vue registers itself with the parent form via an event emitter:

import { emitter } from "../../emitter";
const items = ref<FormItem[]>([]);

emitter.on("addFormItem", item => {
  items.value.push(item);
});

During onMounted, if the component has a prop, it subscribes to a global validate event and notifies the parent:

onMounted(() => {
  if (props.prop) {
    emitter.on("validate", () => validate());
    emitter.emit("addFormItem", o);
  }
});

function validate() {
  if (!formData?.rules) return Promise.resolve({ result: true });
  const schema = new Schema({ [props.prop]: formData.rules[props.prop] });
  return schema.validate({ [props.prop]: formData.model[props.prop] }, errors => {
    error.value = errors ? errors[0].message : "";
  });
}

The relationship among el-form, el-form-item, and input components is a nested hierarchy: the form provides the data model and rules, the input handles user interaction, and the form‑item manages validation and error display.

Component Communication

Vue 3’s provide / inject API replaces the older event‑bus pattern. The form provides a context object containing model and rules:

provide(key, { model: props.model, rules: props.rules });
const formData = inject(key);

Child components obtain this context via inject, ensuring type safety with an InjectionKey:

import { InjectionKey } from "vue";
import { Rules, Values } from "async-validator";

export type FormData = { model: Record<string, unknown>; rules?: Rules };
export type FormItem = { validate: () => Promise<Values> };
export const key: InjectionKey<FormData> = Symbol("form-data");

Parent‑Child Communication

Props pass data downwards, while emit (or the event‑bus) notifies the parent of changes. In Vue 3, provide/inject combined with reactive data offers a clearer, type‑safe data flow.

FAQ – Event Bus vs. Provide/Inject

Using an event‑bus in Vue 2 leads to loose coupling, potential performance overhead, and difficult debugging. Modern Vue 3 prefers provide/inject or state‑management libraries (Pinia, Vuex) for scoped, efficient communication. The example demonstrates how provide/inject simplifies registration, lifecycle handling, and validation coordination.

Form Validation Process

Define validation rules (e.g., required, min/max, custom validator).

Bind the rules to el-form via the :rules attribute.

Optionally implement custom validator functions.

Trigger validation (e.g., on submit) and handle the result.

When validation fails, error messages appear next to the corresponding input; on success, the form can be submitted to the backend.

Summary

The article details the design and implementation of a complex Vue 3 form component, covering data binding, rule definition with async-validator, component hierarchy, and communication via provide/inject. It also compares the legacy event‑bus approach with modern patterns, recommending the latter for maintainable, performant code.

ValidationComponentVueProvide/InjectFormAsyncValidator
JavaEdge
Written by

JavaEdge

First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.