How to use Vue's new DefineModel macro
I'm building out the registration/login flow for my indoor rowing workout tracking application. I need a way that users can toggle the password input from 'password' to 'text' so they can see their password when typing.
Why does DefineModel exist?
Before defineModel
, creating two-way binding in Vue required developers to declare props
, emits
and an event handler
to handle child to parent state changes. This setup becomes difficult to maintain and scale as the application grows and changes.
defineModel()
is a macro that provides a much cleaner and simpler implementation.
This is a very simple example of how Child component can update a count
value passed in from the Parent component. This code below is used in the demo below:
<!-- Parent.vue -->
<script setup lang="ts">
const count = ref(0);
</script>
<template>
<div class="col-span-full my-4 w-full max-w-[600px] mx-auto">
<p class="m-0">Parent's count value: <span class="badge badge-lg badge-primary">{{ count }}</span></p>
</div>
<div class="col-span-full mt-2 mb-4 w-full max-w-[600px] mx-auto">
Child Component below has `v-model:count="count"` and is updating the parent's state:
<br />
<br />
<Counter v-model:count="count" />
</div>
</template>
<!-- Counter.vue -->
<script setup lang="ts">
// Const count is assigned via defineModel() and takes in the 'count' as that's the v-model passed in from the parent.
// We can also assign a type and default to `count`
const count = defineModel('count', { type: Number, default: 0 })
// The update() method can update the `count` value and the parent will automatically see the updated value
const increment = () => count.value++;
</script>
<template>
<p>Child vue showing 'count' from parent: <span class="badge badge-lg badge-secondary">{{ count }}</span></p>
<button @click="increment" class="btn btn-sm btn-secondary">Increment Counter in Child component</button>
</template>
Demo
Parent's count value: 0
Child vue showing 'count' from parent: 0
How to toggle input types
I'm using this to create the following re-usable password type toggle that's displayed in my login and registration forms:
I've created the following inline demo below with the code sample under it too:
Demo
<!-- PasswordIndicator.vue -->
<script setup lang="ts">
const passwordVisibility = defineModel<Boolean>('passwordVisibility');
const togglePasswordVisibility = () => passwordVisibility.value = !passwordVisibility.value;
</script>
<template>
<small @click="togglePasswordVisibility" class="cursor-pointer">
<span>{{ passwordVisibility ? 'ππ½' : 'ππ½'}}</span>
</small>
</template>
<!-- PasswordInput.vue -->
<script setup lang="ts">
const password = ref<string>('');
const canSeePassword = ref<boolean>(false);
</script>
<template>
<div class="col-span-full my-4 w-full max-w-[600px] mx-auto">
<label for="password"
class="input input-bordered flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"
fill="currentColor" class="w-4 h-4 opacity-70">
<path fill-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
clip-rule="evenodd" />
</svg>
<input :type="canSeePassword ? 'text' : 'password'" aria-label="Password for new account"
id="password" placeholder="Password" v-model="password"
autocomplete="new-password" class="grow" required />
<PasswordIndicator v-model:passwordVisibility="canSeePassword" />
</label>
</div>
</template>
The above PasswordIndicator
component can be reused multiple times in a single form if the canSeePassword
ref is changed for each input you'd like to control, e.g: a "new password" and "confirm new password" inputs for a Registration form π.
Wrap up
Hopefully this quick overview of the new defineModel
macro helps you create cleaner and reusable Vue components. You can also use v-model
's .modifiers
with the macro, which will help convert your older code to the newer syntax whilst keeping the same functionality π€.