Components, Mixins, and Functional Components

Building a Vue application is like putting a puzzle together. Each piece of the puzzle is a component, and each piece has a slot to fill.

Components play a big part in Vue development. In Vue, each part of your code will be a component it could be a layout, a page, a container, or a button, but ultimately, it's a component. Learning how to interact with them and reuse them is the key to cleaning up code and performance in your Vue application. Components are the code that will, in the end, render something on the screen, whatever its size might be.

In this chapter, we will learn about how to make a visual component that can be reused in many places. We'll use slots to place data inside our components, create functional components for seriously fast rendering, implement direct communication between parent and child components, and look at loading our components asynchronously.

Then, we'll put all those pieces together and create a beautiful puzzle that's also a Vue application.

In this chapter, we'll cover the following recipes:

  • Creating a visual template component
  • Using slots and named slots to place data inside your components
  • Passing data to your component and validating the data
  • Creating functional components
  • Accessing your children component's data
  • Creating a dynamic injected component
  • Creating a dependency injection component
  • Creating a mixin component
  • Lazy loading your components

Let's get started!

Technical requirements

In this chapter, we will be using Node.js and Vue-CLI.

Attention Windows users: You need to install an npm package called windows-build-tools to be able to install the required packages. To do so, open PowerShell as an administrator and execute the > npm install -g windows-build-tools command.

To install the Vue CLI, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm install -g @vue/cli @vue/cli-service-global

Creating a visual template component

Components can be data-driven, stateless, stateful, or simple visual components. But what is a visual component? A visual component is a component that has only one purpose: visual manipulation.

A visual component could have a simple Scoped CSS with some div HTML elements, or it could be a more complex component that can calculate the position of the element on the screen in real time.

In this recipe, we will create a card wrapper component that follows the Material Design guide.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

How to do it...

To start our component, we need to create a new Vue project with the Vue CLI. Open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> vue create visual-component

The CLI will ask some questions that will help you create the project. You can use the arrow keys to navigate, the Enter key to continue, and the spacebar to select an option. Choose the default option:

? Please pick a preset: (Use arrow keys)
default (babel, eslint)
Manually select features

Now, follow these steps to create a visual template component:

  1. Create a new file called MaterialCardBox.vue in the src/components folder.
  2. In this file, we will start working on the template of our component. We need to create the box for the card. By using the Material Design guide, this box will have a shadow and rounded corners:
<template>
<div class="cardBox elevation_2">
<div class="section">
This is a Material Card Box
</div>
</div>
</template>
  1. In the <script> part of our component, we will add just our basic name:
<script>
export default {
name: 'MaterialCardBox',
};
</script>
  1. We need to create our elevation CSS rules. To do this, create a file named elevation.css in the style folder. There, we will create the elevations from 0 to 24 so that we can follow all the elevations provided by the Material Design guide:
.elevation_0 {
border: 1px solid rgba(0, 0, 0, 0.12);
}

.elevation_1 {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2),
0 1px 1px rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}

.elevation_2 {
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}

.elevation_3 {
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.2),
0 3px 4px rgba(0, 0, 0, 0.14),
0 3px 3px -2px rgba(0, 0, 0, 0.12);
}

.elevation_4 {
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2),
0 4px 5px rgba(0, 0, 0, 0.14),
0 1px 10px rgba(0, 0, 0, 0.12);
}

.elevation_5 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}

.elevation_6 {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 6px 10px rgba(0, 0, 0, 0.14),
0 1px 18px rgba(0, 0, 0, 0.12);
}

.elevation_7 {
box-shadow: 0 4px 5px -2px rgba(0, 0, 0, 0.2),
0 7px 10px 1px rgba(0, 0, 0, 0.14),
0 2px 16px 1px rgba(0, 0, 0, 0.12);
}

.elevation_8 {
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14),
0 3px 14px 2px rgba(0, 0, 0, 0.12);
}

.elevation_9 {
box-shadow: 0 5px 6px -3px rgba(0, 0, 0, 0.2),
0 9px 12px 1px rgba(0, 0, 0, 0.14),
0 3px 16px 2px rgba(0, 0, 0, 0.12);
}

.elevation_10 {
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
0 10px 14px 1px rgba(0, 0, 0, 0.14),
0 4px 18px 3px rgba(0, 0, 0, 0.12);
}

.elevation_11 {
box-shadow: 0 6px 7px -4px rgba(0, 0, 0, 0.2),
0 11px 15px 1px rgba(0, 0, 0, 0.14),
0 4px 20px 3px rgba(0, 0, 0, 0.12);
}

.elevation_12 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 12px 17px 2px rgba(0, 0, 0, 0.14),
0 5px 22px 4px rgba(0, 0, 0, 0.12);
}

.elevation_13 {
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
}

.elevation_14 {
box-shadow: 0 7px 9px -4px rgba(0, 0, 0, 0.2),
0 14px 21px 2px rgba(0, 0, 0, 0.14),
0 5px 26px 4px rgba(0, 0, 0, 0.12);
}

.elevation_15 {
box-shadow: 0 8px 9px -5px rgba(0, 0, 0, 0.2),
0 15px 22px 2px rgba(0, 0, 0, 0.14),
0 6px 28px 5px rgba(0, 0, 0, 0.12);
}

.elevation_16 {
box-shadow: 0 8px 10px -5px rgba(0, 0, 0, 0.2),
0 16px 24px 2px rgba(0, 0, 0, 0.14),
0 6px 30px 5px rgba(0, 0, 0, 0.12);
}

.elevation_17 {
box-shadow: 0 8px 11px -5px rgba(0, 0, 0, 0.2),
0 17px 26px 2px rgba(0, 0, 0, 0.14),
0 6px 32px 5px rgba(0, 0, 0, 0.12);
}

.elevation_18 {
box-shadow: 0 9px 11px -5px rgba(0, 0, 0, 0.2),
0 18px 28px 2px rgba(0, 0, 0, 0.14),
0 7px 34px 6px rgba(0, 0, 0, 0.12);
}

.elevation_19 {
box-shadow: 0 9px 12px -6px rgba(0, 0, 0, 0.2),
0 19px 29px 2px rgba(0, 0, 0, 0.14),
0 7px 36px 6px rgba(0, 0, 0, 0.12);
}

.elevation_20 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 20px 31px 3px rgba(0, 0, 0, 0.14),
0 8px 38px 7px rgba(0, 0, 0, 0.12);
}

.elevation_21 {
box-shadow: 0 10px 13px -6px rgba(0, 0, 0, 0.2),
0 21px 33px 3px rgba(0, 0, 0, 0.14),
0 8px 40px 7px rgba(0, 0, 0, 0.12);
}

.elevation_22 {
box-shadow: 0 10px 14px -6px rgba(0, 0, 0, 0.2),
0 22px 35px 3px rgba(0, 0, 0, 0.14),
0 8px 42px 7px rgba(0, 0, 0, 0.12);
}

.elevation_23 {
box-shadow: 0 11px 14px -7px rgba(0, 0, 0, 0.2),
0 23px 36px 3px rgba(0, 0, 0, 0.14),
0 9px 44px 8px rgba(0, 0, 0, 0.12);
}

.elevation_24 {
box-shadow: 0 11px 15px -7px rgba(0, 0, 0, 0.2),
0 24px 38px 3px rgba(0, 0, 0, 0.14),
0 9px 46px 8px rgba(0, 0, 0, 0.12);
}
  1. For styling our card in the <style> part of the component, we need to set the scoped attribute inside the <style> tag. This ensures that the visual style won't interfere with any other components within our application. We will make this card follow the Material Design guide. We need to import the Roboto font family and apply it to all the elements that will be wrapped inside this component:
<style scoped>
@import url('https://fonts.googleapis.com/css?
family=Roboto:400,500,700&display=swap');
@import '../style/elevation.css';

* {
font-family: 'Roboto', sans-serif;
}

.cardBox {
width: 100%;
max-width: 300px;
background-color: #fff;
position: relative;
display: inline-block;
border-radius: 0.25rem;
}

.cardBox > .section {
padding: 1rem;
position: relative;
}
</style>
  1. In the App.vue file, we need to import our component to be able to see it:
<template>
<div id='app'>
<material-card-box />
</div>
</template>

<script>
import MaterialCardBox from './components/MaterialCardBox.vue';

export default {
name: 'app',
components: {
MaterialCardBox
}
}
</script>
  1. To run the server and see your component, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:
> npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

How it works...

A visual component is a component that will wrap any component and place the wrapped data alongside custom styles. Since this component mixes with others, it can form a new component without you needing to reapply or rewrite any style in your code.

See also

Using slots and named slots to place data inside your components

Sometimes, the pieces of the puzzle go missing, and you find yourself with a blank spot. Imagine that you could fill that empty spot with a piece that you crafted yourself not the original one that came with the puzzle box. That's a rough analogy for what a Vue slot is.

Vue slots are like open spaces in your component that other components can fill with text, HTML elements, or other Vue components. You can declare where the slot will be and how it will behave in your component.

With this technique, you can create a component and, when needed, customize it without any effort at all.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Creating a visual template component recipe.

How to do it...

Follow these instructions to create slots and named slots in components:

  1. Open the MaterialCardBox.vue file in the components folder.
  2. In the <template> part of the component, we will need to add four main sections to the card. These sections are based on the Material Design card's anatomy and are the header, media, main section, and action areas. We will use the default slot for main section; the rest will all be named scopes. For some named slots, we will add a fallback configuration that will be displayed if the user doesn't choose any setting for the slot:
<template>
<div class="cardBox elevation_2">
<div class="header">
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
Card Header
</h1>
<h2 class="cardSubHeader cardText">
Card Sub Header
</h2>
</div>
</div>
<div class="media">
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
src="https://via.placeholder.com/350x250"
>
</div>
<div
v-if="$slots.default"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot/>
</div>
<div
v-if="$slots.action"
class="action"
>
<slot name="action"/>
</div>
</div>
</template>
  1. Now, we need to create our text CSS rules for the component. In the style folder, create a new file called cardStyles.css. Here, we will add the rules for the card's text and headers:
h1, h2, h3, h4, h5, h6 {
margin: 0;
}

.cardText {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
text-decoration: inherit;
text-transform: inherit;
font-size: 0.875rem;
line-height: 1.375rem;
letter-spacing: 0.0071428571em;
}

h1.cardHeader {
font-size: 1.25rem;
line-height: 2rem;
font-weight: 500;
letter-spacing: .0125em;
}

h2.cardSubHeader {
font-size: .875rem;
line-height: 1.25rem;
font-weight: 400;
letter-spacing: .0178571429em;
opacity: .6;
}
  1. In the <style> part of the component, we need to create some CSS that will follow the rules of our design guide:
<style scoped>
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");
@import "../style/elevation.css";
@import "../style/cardStyles.css";

* {
font-family: "Roboto", sans-serif;
}

.cardBox {
width: 100%;
max-width: 300px;
border-radius: 0.25rem;
background-color: #fff;
position: relative;
display: inline-block;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2), 0 2px 2px rgba(0, 0,
0, 0.14)
,
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}

.cardBox > .header {
padding: 1rem;
position: relative;
display: block;
}

.cardBox > .media {
overflow: hidden;
position: relative;
display: block;
max-width: 100%;
}

.cardBox > .section {
padding: 1rem;
position: relative;
margin-bottom: 1.5rem;
display: block;
}

.cardBox > .action {
padding: 0.5rem;
position: relative;
display: block;
}

.cardBox > .action > *:not(:first-child) {
margin-left: 0.4rem;
}

.noBottomPadding {
padding-bottom: 0 !important;
}

.halfPaddingTop {
padding-top: 0.5rem !important;
}
</style>
  1. In the App.vue file, in the src folder, we need to add elements to these slots. These elements will be added to each one of the named slots, as well as the default slot. We will change the component inside the <template> part of the file. To add a named slot, we need to use a directive called v-slot: and then add the name of the slot we want to use:
<template>
<div id="app">
<MaterialCardBox>
<template v-slot:header>
<strong>Card Title</strong><br>
<span>Card Sub-Title</span>
</template>
<template v-slot:media>
<img src="https://via.placeholder.com/350x150">
</template>
<p>Main Section</p>
<template v-slot:action>
<button>Action Button</button>
<button>Action Button</button>
</template>
</MaterialCardBox>
</div>
</template>
For the default slot, we don't need to use a directive; it just needs to be wrapped inside the component so that it can placed inside the <slot /> part of the component.
  1. To run the server and see your component, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

How it works...

Slots are places where you can put anything that can be rendered into the DOM. We choose the position of our slot and tell the component where to render when it receives any information.

In this recipe, we used named slots, which are designed to work with a component that requires more than one slot. To place any information inside that component within the Vue single file (.vue) <template> part, you need to add the v-slot: directive so that Vue knows where to place the information that was passed down.

See also

Passing data to your component and validating the data

At this point, you know how to place data inside your component through slots, but those slots were made for HTML DOM elements or Vue components. Sometimes, you need to pass data such as strings, arrays, Booleans, or even objects.

The whole application is like a puzzle, where each piece is a component. Communication between components is an important part of this. The possibility to pass data to a component is the first step when it comes to connecting the puzzle, while validating the data is the final step for connecting the pieces.

In this recipe, we will learn how to pass data to a component and validate the data that was passed to it.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will continue using the project from the Using slots and named slots to place data inside your components recipe.

How to do it...

Follow these instructions to pass data to the component and validate it:

  1. Open the MaterialCardBox.vue file inside the src/components folder.
  2. In the <script> part of the component, we will create a new property called props. This property receives the component's data, which can be used for visual manipulation, variables inside your code, or for a function that needs to be executed. In this property, we need to declare the name of the attribute, its type, if it's required, and the validation function. This function will be executed at runtime to validate whether the attribute that has been passed is a valid one:
<script>
export default {
name: 'MaterialCardBox',
inheritAttrs: false,
props: {
header: {
type: String,
required: false,
default: '',
validator: (v) => typeof v === 'string',
},
subHeader: {
type: String,
required: false,
default: '',
validator: (v) => typeof v === 'string',
},
mainText: {
type: String,
required: false,
default: '',
validator: (v) => typeof v === 'string',
},
showMedia: {
type: Boolean,
required: false,
default: false,
validator: (v) => typeof v === 'boolean',
},
imgSrc: {
type: String,
required: false,
default: '',
validator: (v) => typeof v === 'string',
},
showActions: {
type: Boolean,
required: false,
default: false,
validator: (v) => typeof v === 'boolean',
},
elevation: {
type: Number,
required: false,
default: 2,
validator: (v) => typeof v === 'number',
},
},
computed: {},
};
</script>
  1. In the computed property, in the <script> part of the component, we need to create a set of visual manipulation rules that will be used to render the card. These rules are called showMediaContent, showActionsButtons, showHeader, and cardElevation. Each rule will check the received props and the $slots objects to check whether the relevant card part needs to be rendered:
computed: {
showMediaContent() {
return (this.$slots.media || this.imgSrc) && this.showMedia;
},
showActionsButtons() {
return this.showActions && this.$slots.action;
},
showHeader() {
return this.$slots.header || (this.header || this.subHeader);
},
showMainContent() {
return this.$slots.default || this.mainText;
},
cardElevation() {
return `elevation_${parseInt(this.elevation, 10)}`;
},
},
  1. After adding the visual manipulation rules, we need to add the created rules to the <template> part of our component. They will affect the appearance and behavior of our card. For example, if no header slot has been defined but a header property has been defined, we'll show the fallback header. This header contains the data that was passed down via props:
<template>
<div
class="cardBox"
:class="cardElevation"
>
<div
v-if="showHeader"
class="header"
>
<slot
v-if="$slots.header"
name="header"
/>
<div v-else>
<h1 class="cardHeader cardText">
{{ header }}
</h1>
<h2 class="cardSubHeader cardText">
{{ subHeader }}
</h2>
</div>
</div>
<div
v-if="showMediaContent"
class="media"
>
<slot
v-if="$slots.media"
name="media"
/>
<img
v-else
:src="imgSrc"
>
</div>
<div
v-if="showMainContent"
class="section cardText"
:class="{
noBottomPadding: $slots.action,
halfPaddingTop: $slots.media,
}"
>
<slot v-if="$slots.default" />
<p
v-else
class="cardText"
>
{{ mainText }}
</p>
</div>
<div
v-if="showActionsButtons"
class="action"
>
<slot
v-if="$slots.action"
name="action"
/>
</div>
</div>
</template>
  1. To run the server and see your component, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:
> npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

How it works...

Each Vue component is a JavaScript object that has a render function. This render function is called when it is time to render it in the HTML DOM. A single-file component is an abstraction of this object.

When we are declaring that our component has unique props that can be passed, it opens a tiny door for other components or JavaScript to place information inside our component. We are then able to use those values inside our component to render data, do some calculations, or make visual rules.

In our case, using the single-file component, we are passing those rules as HTML attributes because vue-template-compiler will take those attributes and transform them into JavaScript objects.

When those values are passed to our component, Vue checks whether the passed attribute matches the correct type, and then we execute our validation function on top of each value to see whether it matches what we'd expect.

Once all of this is done, the component's life cycle continues, and we can render our component.

See also

Creating functional components

The beauty of functional components is their simplicity. They are stateless components without any data, computed properties, or even life cycles. They are just render functions that are called when the data that has been passed changes.

You may be wondering how this can be useful. Well, a functional component is a perfect companion for UI components that don't need to keep any data inside them, or visual components that are just rendered components that don't require any data manipulation.

As the name implies, they are similar to function components, and they have nothing more than the render function. They are a stripped-down version of a component that's used exclusively for performance rendering and visual elements.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Passing data to your component and validating the data recipe.

How to do it...

Follow these instructions to create a Vue functional component:

  1. Create a new file called MaterialButton.vue inside the src/components folder.
  2. In this component, we need to validate whether the prop we'll receive is a valid color. To do this, install the is-color module inside the project. You'll need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:
> npm install --save is-color
  1. In the <script> part of our component, we need to create the props object that the functional component will receive. As a functional component is just a render function with no state, it's stateless – the <script> part of the component is trimmed down to props, injections, and slots. There will be four props objects: backgroundColor, textColor, isRound, and isFlat. These won't be required when we're installing the component as we will have a default value defined in props:
<script>
import isColor from 'is-color';

export default {
name: 'MaterialButton',
props: {
backgroundColor: {
type: String,
required: false,
default: '#fff',
validator: (v) => typeof v === 'string' && isColor(v),
},
textColor: {
type: String,
required: false,
default: '#000',
validator: (v) => typeof v === 'string' && isColor(v),
},
isRound: {
type: Boolean,
required: false,
default: false,
},
isFlat: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
  1. We need to create a button HTML element with a basic class attribute button and a dynamic class attribute based on the props object that's received. Compared to the normal component, we need to specify the props property in order to use the functional component. For the style of the button, we need to create a dynamic style attribute, also based on $props. To emit all the event listeners directly to the parent, we can call the v-bind directive and pass the $attrs property. This will bind all the event listeners without us needing to declare each one. Inside the button, we will add a div HTML element for visual enhancement and add <slot> where the text will be placed:
<template>
<button
tabindex="0"
class="button"
:class="{
round: $props.isRound,
isFlat: $props.isFlat,
}"
:style="{
background: $props.backgroundColor,
color: $props.textColor
}"
v-bind="$attrs"
>
<div
tabindex="-1"
class="button_focus_helper"
/>
<slot/>
</button>
</template>
  1. Now, let's make it pretty. In the <style> part of the component, we need to create all the CSS rules for this button. We need to add the scoped attribute to <style> so that the CSS rules won't affect any other elements in our application:
<style scoped>
.button {
user-select: none;
position: relative;
outline: 0;
border: 0;
border-radius: 0.25rem;
vertical-align: middle;
cursor: pointer;
padding: 4px 16px;
font-size: 14px;
line-height: 1.718em;
text-decoration: none;
color: inherit;
background: transparent;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
min-height: 2.572em;
font-weight: 500;
text-transform: uppercase;
}
.button:not(.isFlat){
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2),
0 2px 2px rgba(0, 0, 0, 0.14),
0 3px 1px -2px rgba(0, 0, 0, 0.12);
}

.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1);
}

.button:not(.isFlat):focus:before,
.button:not(.isFlat):active:before,
.button:not(.isFlat):hover:before {
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2),
0 5px 8px rgba(0, 0, 0, 0.14),
0 1px 14px rgba(0, 0, 0, 0.12);
}

.button_focus_helper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
border-radius: inherit;
outline: 0;
opacity: 0;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5,
1),
opacity 0.4s cubic-bezier(0.25, 0.8, 0.5, 1);
}

.button_focus_helper:after, .button_focus_helper:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
border-radius: inherit;
transition: background-color 0.3s cubic-bezier(0.25, 0.8, 0.5,
1),
opacity 0.6s cubic-bezier(0.25, 0.8, 0.5, 1);
}

.button_focus_helper:before {
background: #000;
}

.button_focus_helper:after {
background: #fff;
}

.button:focus .button_focus_helper:before,
.button:hover .button_focus_helper:before {
opacity: .1;
}

.button:focus .button_focus_helper:after,
.button:hover .button_focus_helper:after {
opacity: .6;
}

.button:focus .button_focus_helper,
.button:hover .button_focus_helper {
opacity: 0.2;
}

.round {
border-radius: 50%;
}
</style>
  1. In the App.vue file, we need to import our component to be able to see it:
<template>
<div id="app">
<MaterialCardBox
header="Material Card Header"
sub-header="Card Sub Header"
show-media
show-actions
img-src="https://picsum.photos/300/200"
:main-text="`
The path of the righteous man is beset on all sides by the
iniquities of the selfish and the tyranny of evil men.`"
>
<template v-slot:action>
<MaterialButton
background-color="#027be3"
text-color="#fff"
>
Action 1
</MaterialButton>
<MaterialButton
background-color="#26a69a"
text-color="#fff"
is-flat
>
Action 2
</MaterialButton>
</template>
</MaterialCardBox>
</div>
</template>

<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
import MaterialButton from './components/MaterialButton.vue';

export default {
name: 'App',
components: {
MaterialButton,
MaterialCardBox,
},
};
</script>
<style>
body {
font-size: 14px;
}
</style>
  1. To run the server and see your component, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:
npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

How it works...

Functional components are as simple as render functions. They don't have any sort of data, functions, or access to the outside world.

They were first introduced in Vue as a JavaScript object render() function only; later, they were added to vue-template-compiler for the Vue single-file application.

A functional component works by receiving two arguments: createElement and context. As we saw in the single file, we only had access to the elements as they weren't in the this property of the JavaScript object. This occurs because as the context is passed to the render function, there is no this property.

A functional component provides the fastest rendering possible on Vue as it doesn't depend on the life cycle of a component to check for the rendering; it just renders each time data is changed.

See also

Accessing your children component's data

Normally, parent-child communications are done via events or props. But sometimes, you need to access data, functions, or computed properties that exist in the child or the parent function.

Vue provides a way for us to interact in both ways, thereby opening doors to communications and events such as props and event listeners.

There is another way to access the data between the components: by using direct access. This can be done with the help of a special attribute in the template when using the single-file component, or by making a direct call to the object inside the JavaScript. This method is seen by some as a little lazy, but there are times when there really is no other way to do it than this.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Creating functional components recipe.

How to do it...

We're going to separate this recipe into four parts. The first three parts will cover the creation of new components – StarRatingInput, StarRatingDisplay, and StarRating – while the last part will cover the direct parent-child manipulation of the data and function's access.

Creating the star rating input

In this recipe, we are going to create a star rating input, based on a five-star ranking system.

Follow these steps to create a custom star rating input:

  1. Create a new file called StarRatingInput.vue in the src/components folder.
  2. In the <script> part of the component, create a maxRating property in the props property that is a number, non-required, and has a default value of 5. In the data property, we need to create our rating property, with a default value of 0. In the methods property, we need to create three methods: updateRating, emitFinalVoting, and getStarName. The updateRating method will save the rating to the data, emitFinalVoting will call updateRating and emit the rating to the parent component through a final-vote event, and getStarName will receive a value and return the icon name of the star:
<script>
export default {
name: 'StarRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
  1. In the <template> part of the component, we need to create a <slot> component so that we can place the text before the star rating. We'll create a dynamic list of stars based on the maxRating value that we received via the props property. Each star that is created will have a listener attached to it in the mouseenter, focus, and click events. mouseenter and focus, when fired, will call the updateRating method, and click will call emitFinalVote:
<template>
<div class="starRating">
<span class="rateThis">
<slot/>
</span>
<ul>
<li
v-for="rate in maxRating"
:key="rate"
@mouseenter="updateRating(rate)"
@click="emitFinalVote(rate)"
@focus="updateRating(rate)"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
</div>
</template>
  1. We need to import the Material Design icons into our application. Create a new styling file in the styles folder called materialIcons.css and add the CSS rules for font-family:
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialicons/v48/flUhRq6tzZclQEJ- Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
}

.material-icons {
font-family: 'Material Icons' !important;
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
  1. Open the main.js file and import the created stylesheet into it. The css-loader webpack will process the imported .css files in JavaScript files. This will help with development because you don't need to reimport the file elsewhere:
import { createApp } from 'vue';
import App from './App.vue';
import './style/materialIcons.css';

createApp(App).mount('#app');
  1. To style our component, we will create a common styling file in the src/style folder called starRating.css. There, we will add the common styles that will be shared between the StarRatingDisplay and StarRatingInput components:
.starRating {
user-select: none;
display: flex;
flex-direction: row;
}

.starRating * {
line-height: 0.9rem;
}

.starRating .material-icons {
font-size: .9rem !important;
color: orange;
}

ul {
display: inline-block;
padding: 0;
margin: 0;
}

ul > li {
list-style: none;
float: left;
}
  1. In the <style> part of the component, we need to create all the CSS rules. Then, inside the StarRatingInput.vue component file located in the src/components folder, we need to add the scoped attribute to <style> so that none of the CSS rules affect any of the other elements in our application. Here, we will import the common styles that we created and add new ones for the input:
<style scoped>
@import '../style/starRating.css';

.starRating {
justify-content: space-between;
}

.starRating * {
line-height: 1.7rem;
}

.starRating .material-icons {
font-size: 1.6rem !important;
}

.rateThis {
display: inline-block;
color: rgba(0, 0, 0, .65);
font-size: 1rem;
}
</style>
  1. To run the server and see your component, you will need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

Creating the StarRatingDisplay component

Now that we have our input, we need a way to display the selected choice to the user. Follow these steps to create a StarRatingDisplay component:

  1. Create a new component called StarRatingDisplay.vue in the src/components folder.
  2. In the <script> part of the component, in the props property, we need to create three new properties: maxRating, rating, and votes. All three of them will be numbers, non-required and have a default value. In the methods property, we need to create a new method called getStarName, which will receive a value and return the icon name of the star:
<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
  1. In <template>, we need to create a dynamic list of stars based on the maxRating value that we received via the props property. After the list, we need to display that we received votes, and if we receive any more votes, we will display them too:
<template>
<div class="starRating">
<ul>
<li
v-for="rate in maxRating"
:key="rate"
>
<i class="material-icons">
{{ getStarName(rate) }}
</i>
</li>
</ul>
<span class="rating">
{{ rating }}
</span>
<span
v-if="votes"
class="votes"
>
({{ votes }})
</span>
</div>
</template>
  1. In the <style> part of the component, we need to create all the CSS rules. We need to add the scoped attribute to <style> so that none of the CSS rules affect any of the other elements in our application. Here, we will import the common styles that we created and add new ones for the display:
<style scoped>
@import '../style/starRating.css';

.rating, .votes {
display: inline-block;
color: rgba(0, 0, 0, .65);
font-size: .75rem;
margin-left: .4rem;
}
</style>
  1. To run the server and see your component, you need to open a Terminal (macOS or Linux) or Command Prompt/PowerShell (Windows) and execute the following command:

> npm run serve
Remember to always execute the command npm run lint --fix, to automatically fix any code lint error.

Here is the component rendered and running:

Creating the StarRating component

Now that we've created the input and the display, we need to join them together inside a single component. This component will be the final component that we'll use in the application.

Follow these steps to create the final StarRating component:

  1. Create a new file called StarRating.vue in the src/components folder.
  2. In the <script> part of the component, we need to import the StarRatingDisplay and StarRatingInput components. In the props property, we need to create three new properties: maxRating, rating, and votes. All three of them will be numbers, non-required, and have a default value. In the data property, we need to create our rating property, with a default value of 0, and a property called voted, with a default value of false. In the methods property, we need to add a new method called vote, which will receive rank as an argument. It will define rating as the received value and define the inside variable of the voted component as true:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';

export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
  1. For the <template> part, we will place both components here, thereby displaying the input of the rating:
<template>
<div>
<StarRatingInput
v-if="!voted"
:max-rating="maxRating"
@final-vote="vote"
>
Rate this Place
</StarRatingInput>
<StarRatingDisplay
v-else
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
/>
</div>
</template>

Data manipulation on child components

Now that all of our components are ready, we need to add them to our application. The base application will access the child component, and it will set the rating to 5 stars.

Follow these steps to understand and manipulate the data in the child components:

  1. In the App.vue file, in the <template> part of the component, remove the main-text attribute of the MaterialCardBox component and set it as the default slot of the component.
  2. Before the placed text, we will add the StarRating component. We will add a ref attribute to it. This attribute will tell Vue to link this component directly to a special property in the this object of the component. In the action buttons, we will add the listeners for the click event one for resetVote and another for forceVote:
<template>
<div id="app">
<MaterialCardBox
header="Material Card Header"
sub-header="Card Sub Header"
show-media
show-actions
img-src="https://picsum.photos/300/200"
>
<p>
<StarRating
ref="starRating"
/>
</p>
<p>
The path of the righteous man is beset on all sides by the
iniquities of the selfish and the tyranny of evil men.
</p>
<template v-slot:action>
<MaterialButton
background-color="#027be3"
text-color="#fff"
@click="resetVote"
>
Reset
</MaterialButton>
<MaterialButton
background-color="#26a69a"
text-color="#fff"
is-flat
@click="forceVote"
>
Rate 5 Stars
</MaterialButton>
</template>
</MaterialCardBox>
</div>
</template>
  1. In the <script> part of the component, we will create a methods property and add two new methods: resetVote and forceVote. These methods will access the StarRating component and reset the data or set the data to a 5-star vote, respectively:
<script>
import MaterialCardBox from './components/MaterialCardBox.vue';
import MaterialButton from './components/MaterialButton.vue';
import StarRating from './components/StarRating.vue';

export default {
name: 'App',
components: {
StarRating,
MaterialButton,
MaterialCardBox,
},
methods: {
resetVote() {
this.$refs.starRating.vote(0);
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.vote(5);
},
},

How it works...

When the ref property is added to the component, Vue adds a link to the referenced element to the $refs property inside the this property object of JavaScript. From there, you have full access to the component.

This method is commonly used to manipulate HTML DOM elements without the need to call for document query selector functions.

However, the main function of this property is to give access to the Vue component directly, enabling you to execute functions and see the computed properties, variables, and changed variables of the component – this is like having full access to the component from the outside.

There's more...

In the same way that a parent can access a child component, a child can access a parent component by calling $parent on the this object. An event can access the root element of the Vue application by calling the $root property.

See also

You can find out more information about parent-child communication at https://v3.vuejs.org/guide/migration/custom-directives.html#edge-case-accessing-the-component-instance.

Creating a dynamically injected component

There are some cases where your component can be defined by the kind of variable you are receiving or the type of data that you have; then, you need to change the component on the fly, without the need to set a lot of Vue v-if, v-else-if, and v-else directives.

In those cases, the best thing to do is use dynamic components, when a computed property or a function can define the component that will be used to be rendered, and the decision is made in real time.

These decisions can sometimes be simple to make if there are two responses, but they can be more complex if there's a long switch case, where you may have a long list of possible components that need to be used.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:
  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Accessing your children components data recipe.

How to do it...

Follow these steps to create a dynamically injected component:

  1. Open the StarRating.vue component.
  2. In the <script> part of the component, we need to create a computed property with a new computed value called starComponent. This value will check whether the user has voted. If they haven't, it will return the StarRatingInput component; otherwise, it will return the StarRatingDisplay component:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';

export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
  1. In the <template> part of the component, we will remove both of the existing components and replace them with a special component called <component>. This special component has a named attribute that you can point to anywhere that returns a valid Vue component. In our case, we will point to the computed starComponent property. We will take all the bind props that were defined by both of the other components and put them inside this new component, including the text that has been placed inside <slot>:
<template>
<component
:is="starComponent"
:max-rating="maxRating"
:rating="rating || rank"
:votes="votes"
@final-vote="vote"
>
Rate this Place
</component>
</template>

How it works...

Using the Vue special <component> component, we declared what the component should render according to the rules that were set on the computed property.

Being a generic component, you always need to guarantee that everything will be there for each of the components that can be rendered. The best way to do this is by using the v-bind directive with the props and rules that need to be defined, but it's possible to define it directly on the component as well since it will be passed down as a prop.

See also

You can find more information about dynamic components at https://v3.vuejs.org/guide/component-dynamic-async.html#dynamic-async-components.

Creating a dependency injection component

Accessing data directly from a child or a parent component without knowing whether they exist can be very dangerous.

In Vue, it's possible to make your component behavior like an interface and have a common and abstract function that won't change in the development process. The process of dependency injection is a common paradigm in the developing world and has been implemented in Vue as well.

There are some pros and cons to using Vue's internal dependency injection, but it is always a good way to make sure that your children components know what to expect from the parent component when you're developing it.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:
  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Creating a dynamically injected component recipe.

How to do it...

Follow these steps to create a dependency injection component:

  1. Open the StarRating.vue component.
  2. In the <script> part of the component, add a new property called provide. In our case, we will just be adding a key-value to check whether the component is a child of the specific component. Create an object in the property with the starRating key and the true value:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';

export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
provide: {
starRating: true,
},
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
  1. Open the StarRatingDisplay.vue file.
  2. In the <script> part of the component, we will add a new property called inject. This property will receive an object with a key named starRating, and the value will be an object that will have a default() function.

This function will log an error if this component is not a child of the StarRating component:

<script>
export default {
name: 'StarRatingDisplay',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating'
);
},
},
},
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>
  1. Open the StarRatingInput.vue file.
  2. In the <script> part of the component, we will add a new property called inject. This property will receive an object with a key named starRating, and the value will be an object that will have a default() function. This function will log an error if this component is not a child of the StarRating component:
<script>
export default {
name: 'StartRatingInput',
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
},
inject: {
starRating: {
default() {
console.error('StarRatingInput need to be a child of
StartRating'
);
},
},
},
data: () => ({
rating: 0,
}),
methods: {
updateRating(value) {
this.rating = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rating);
},
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>

How it works...

At runtime, Vue will check for the injected property of starRating in the StarRatingDisplay and StarRatingInput components, and if the parent component does not provide this value, it will log an error to the console.

Using component injection is commonly used to provide and maintain a common interface between bounded components, such as a menu and an item. An item may need some function or data that is stored in the menu, or we may need to check whether it's a child of the menu.

The main downside of dependency injection is that there is no more reactivity on the shared element. Because of this, it's mostly used to share functions or check component links.

See also

You can find more information about component dependency injection at https://v3.vuejs.org/guide/component-provide-inject.html#provide-inject.

Creating a component mixin

There are times when you will find yourself rewriting the same code over and over. However, there is a way to prevent this and make yourself far more productive.

For this, you can use what is called a mixin, a special code import in Vue that joins code parts from outside your component to your current component.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for his recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Creating a dependency injection component recipe.

How to do it...

Follow these steps to create a component mixin:

  1. Open the StarRating.vue component.
  2. In the <script> part, we need to extract the props property into a new file called starRatingDisplay.js that we need to create in the mixins folder. This new file will be our first mixin, and will look like this:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
votes: {
type: Number,
required: false,
default: 0,
},
},
};
  1. Back in the StarRating.vue component, we need to import this newly created file and add it to a new property called mixin:
<script>
import StarRatingInput from './StarRatingInput.vue';
import StarRatingDisplay from './StarRatingDisplay.vue';
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';

export default {
name: 'StarRating',
components: { StarRatingDisplay, StarRatingInput },
mixins: [StarRatingDisplayMixin],
provide: {
starRating: true,
},
data: () => ({
rank: 0,
voted: false,
}),
computed: {
starComponent() {
if (!this.voted) return StarRatingInput;
return StarRatingDisplay;
},
},
methods: {
vote(rank) {
this.rank = rank;
this.voted = true;
},
},
};
</script>
  1. Now, we will open the StarRatingDisplay.vue file.
  2. In the <script> part, we will extract the inject property into a new file called starRatingChild.js, which will be created in the mixins folder. This will be our mixin for the inject property:
export default {
inject: {
starRating: {
default() {
console.error('StarRatingDisplay need to be a child of
StarRating');
},
},
},
};
  1. Back in the StarRatingDisplay.vue file, in the <script> part, we will extract the methods property into a new file called starRatingName.js, which will be created in the mixins folder. This will be our mixin for the getStarName method:
export default {
methods: {
getStarName(rate) {
if (rate <= this.rating) {
return 'star';
}
if (Math.fround((rate - this.rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
  1. Back in the StarRatingDisplay.vue file, we need to import those newly created files and add them to a new property called mixin:
<script>
import StarRatingDisplayMixin from '../mixins/starRatingDisplay';
import StarRatingNameMixin from '../mixins/starRatingName';
import StarRatingChildMixin from '../mixins/starRatingChild';

export default {
name: 'StarRatingDisplay',
mixins: [
StarRatingDisplayMixin,
StarRatingNameMixin,
StarRatingChildMixin,
],
};
</script>
  1. Open the StarRatingInput.vue file.
  2. In the <script> part, remove the inject properties and extract the props property into a new file called starRatingBase.js, which will be created in the mixins folder. This will be our mixin for the props property:
export default {
props: {
maxRating: {
type: Number,
required: false,
default: 5,
},
rating: {
type: Number,
required: false,
default: 0,
},
},
};
  1. Back in the StarRatingInput.vue file, we need to rename the rating data property to rank, and in the getStarName method, we need to add a new constant that will receive either the rating props or the rank data. Finally, we need to import starRatingChildMixin and starRatingBaseMixin:
<script>
import StarRatingBaseMixin from '../mixins/starRatingBase';
import StarRatingChildMixin from '../mixins/starRatingChild';

export default {
name: 'StarRatingInput',
mixins: [
StarRatingBaseMixin,
StarRatingChildMixin,
],
data: () => ({
rank: 0,
}),
methods: {
updateRating(value) {
this.rank = value;
},
emitFinalVote(value) {
this.updateRating(value);
this.$emit('final-vote', this.rank);
},
getStarName(rate) {
const rating = (this.rating || this.rank);
if (rate <= rating) {
return 'star';
}
if (Math.fround((rate - rating)) < 1) {
return 'star_half';
}
return 'star_border';
},
},
};
</script>

How it works...

Mixins merge objects together, but make sure you don't replace an already existing property in your component with an imported one.

The order of the mixins properties is important as well, as they will be checked and imported as a for loop, so the last mixin won't change any properties from any of their ancestors.

Here, we took a lot of repeated parts of our code and split them into four different small JavaScript files that are easier to maintain and improve productivity without us needing to rewrite code.

See also

You can find more information about mixins at https://v3.vuejs.org/guide/mixins.html#mixins.

Lazy loading your components

webpack and Vue were born to be together. When using webpack as the bundler for your Vue project, it's possible to make your components load asynchronously or when they are needed. This is commonly known as lazy loading.

Getting ready

The prerequisite for this recipe is Node.js 12+.

The Node.js global objects that are required for this recipe are as follows:

  • @vue/cli
  • @vue/cli-service-global

To complete this recipe, we will use our Vue project and the Vue CLI, as we did in the Creating a component mixin recipe.

How to do it...

Follow these steps to import your component with a lazy loading technique:

  1. Open the App.vue file.
  2. In the <script> part of the component, import the defineAsyncComponent API from Vue and pass the lazyLoad component function as an argument of the defineAsyncComponent function:
<script>
import { defineAsyncComponent } from 'vue';
import StarRating from './components/StarRating.vue';
export default {
name: 'App',
components: {
StarRating,
MaterialButton: defineAsyncComponent(() => import('./components/MaterialButton.vue')),
MaterialCardBox: defineAsyncComponent(() => import('./components/MaterialCardBox.vue')),
},
methods: {
resetVote() {
this.$refs.starRating.vote(0);
this.$refs.starRating.voted = false;
},
forceVote() {
this.$refs.starRating.vote(5);
},
},
};
</script>

<style>
body {
font-size: 14px;
}
</style>

How it works...

Vue now uses a new API called defineAsyncComponent to identify a component as an asynchronous component and receives as an argument, another function that returns the import() method.

When we declare a function that returns an import() function for each component, webpack knows that this import function will be code-splitting, and it will make the component a new file on the bundle.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.225.35.81