Skip to main content

Components

Reusable blocks of UI, which contain

  • template
  • script
  • style

Use v-base with code snippets to create base

<template>
<div>
{{ msg }}
</div>
</template>

<script>
export default {
data() {
return {
msg: "Hello Vue!",
};
},
};
</script>

<style scoped>
</style>

Import and define component in App

<template>
<div>
<Greet />
</div>
</template>

<script>
import Greet from "./components/Greet.vue";
export default {
name: "App",
components: {
Greet,
},
data() {
return {
};
},
};
</script>

Props

<template>
<div>Hello {{ name }}!</div>
</template>

<script>
export default {
name: "Greet",
props: ["name"],
};
</script>
<template>
<div>
<Greet name="Vue" />
<Greet :name="name" />
</div>
</template>

<script>
import Greet from "./components/Greet.vue";
export default {
name: "App",
components: {
Greet,
},
data() {
return {
name: "World",
};
},
};
</script>

To pass dynamic props use v-bind directive or shorthand v-bind :name

Convention is to use kebab-case

Prop Types

props: {
name: {
type: String,
required: true,
default: 'Default Product name'
}
price: Number,
in-stock: Boolean,
}

//App
<Product name="SomeName" :price="500" :in-stock="true" />

NOTE: Use v-bind for non-string values (numbers, booleans) to be typechecked correctly


Non-Prop attributes

  • If component returns a root element, non-prop attributes like id, class are applied to the root element
  • If there is no root element, attribute is not applied to any elements
  • If we want it to be applied to a specific element
<template>
<div>
<h2 v-bind="$attrs">{{name}}</h2>
<h3>{{price}}</h3>
</div>
</template>

//App
<Product id="productTitle" :price="500 />
  • if we don't want the root element to take the attribute, specify inheritAttrs
//Product
export default{
name: 'Product',
data(){},
inheritAttrs: false
}

Provide Inject

Solution to avoid prop-drilling

  • Provide value in App
  • Inject in nested component
//App
export default{
name: 'App',
provide: {
username: 'Tony'
}
}

//Settings
<p>{{username}}</p>
export default{
name: 'Settings',
inject: ['username']
}

NOTE: provide does not allow username to be accessed in app template. Change provide to a function to do so

//App
<p>{{username}}</p>
export default{
name: 'App',
data(){
return{
name: 'Tony'
}
},
provide() {
return{
username: this.name
}
}
}

//Settings
<p>{{username}}</p>
export default{
name: 'Settings',
inject: ['username']
}

Component events

Use emits and $emit to trigger custom events and pass data with it

//Popup
<button @click="$emit('close', 'Closing button')">Close button></button>
export default{
name: 'Popup',
emits: ['close']
}

//App
<Popup v-if="showPopup" @close="closePopup">Close button></button>
export default{
name: 'Popup',
methods: {
closePopup(data){
this.showPopup = false;
console.log(data) // logs 'Closing button'
}
}
}

Validating component events

emits: {
close: (msg) => {
if(!msg){
return false
}else{
return true
}
}
}

v-model with Components

emit update:modelValue to listen to input change

//CustomInput
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
export default{
name: 'CustomInput'.
props: {
modelValue: String
}
}

//App
<CustomInput v-model="title" />
export default{
name: 'App'.
data() {
return {
title: 'Mistborn'
}
}
}

Component Styles

By default styles are applied globally

In this case, red is applied according to normal css (bottom most override takes preference)

// App
<style>
h4{
color: green;
}
</style>

// Child
<style>
h4{
color: red;
}
</style>

scoped attribute applied style to only its own HTML elements

NOTE: even with scoped, parents styles will apply to the childs ROOT element for layout purposes

NOTE: when using slots parents styles will take preference

<style scoped>

Check CSS modules with vue


Dynamic Components

Tab with different components can be done with v-if and managing activeTab state, but this is very verbose and not optimal

use <component :is="registeredName" /> to render component

<button @click="activeTab = 'TabA'">TabA</button>
<button @click="activeTab = 'TabB'">TabB</button>
<component :is="activeTab" />

import TabA from "./components/TabA.vue";
import TabB from "./components/TabB.vue";
export default {
name: "App",
components: {
TabA,
TabB,
},
data() {
return {
activeTab: "TabA",
};
},
};

Keeping alive dynamic components

In the above case if we have multi step form divided into tabs, switching between tabs clears entered data as vue creates new instance each time for performance

use <keepalive> tag to keep the component alive between switches

<keepalive>
<component :is="activeTab" />
</keepalive>