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>