Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Building Vue.js Applications with GraphQL
Building Vue.js Applications with GraphQL

Building Vue.js Applications with GraphQL: Develop a complete full-stack chat app from scratch using Vue.js, Quasar Framework, and AWS Amplify

eBook
€15.99 €23.99
Paperback
€29.99
Subscription
Free Trial
Renews at €18.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Table of content icon View table of contents Preview book icon Preview Book

Building Vue.js Applications with GraphQL

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

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Build a fully functional Vue.js web app and learn how it integrates with GraphQL
  • Transform your chat application into a Progressive Web Application (PWA) for web deployment
  • Discover practical recipes, exploring the capabilities of the GraphQL API for full-stack development using Quasar Framework

Description

Since its release by Facebook in 2012, GraphQL has taken the internet by storm. Huge companies such as Airbnb and Audi have started to adopt it, while small to medium-sized companies are now recognizing the potential of this query-based API. GraphQL may seem strange at first, but as you start to read about and experience more of it, you won’t want to use REST APIs anymore. With the recipes in this book, you will learn how to build a complete real-time chat app from scratch. Starting by creating an AWS Amplify environment, you will delve into developing your first GraphQL Schema. You will then learn how to add the AppSync GraphQL client and create your first GraphQL mutation. The book also helps you to discover the simplicity and data fetching capabilities of GraphQL that make it easy for front-end developers to communicate with the server. You will later understand how to use Quasar Framework to create application components and layouts. Finally, you will find out how to create Vuex modules in your application to manage the app state, fetch data using the GraphQL client, and deploy your application to the web. By the end of this book, you’ll be well versed in proof-of-concept full-stack applications that explore the power of GraphQL with AWS Amplify, and you'll be able to use Quasar Framework to create your Vue applications.

Who is this book for?

This book is for intermediate-level Vue.js developers who want to take their first step toward full-stack development. Prior knowledge of Vue.js and JavaScript is required before getting started with this book.

What you will learn

  • Set up your Vue.js projects with Vue CLI and explore the power of Vue components
  • Discover steps to create functional components in Vue.js for faster rendering
  • Become familiar with AWS Amplify and learn how to set up your environment
  • Understand how to create your first GraphQL schema
  • Use Quasar Framework to create simple and effective interfaces
  • Discover effective techniques to create queries for interacting with data
  • Explore Vuex for adding state management capabilities to your app
  • Discover techniques to deploy your applications effectively to the web
Estimated delivery fee Deliver to Austria

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jan 29, 2021
Length: 298 pages
Edition : 1st
Language : English
ISBN-13 : 9781800565074
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
Estimated delivery fee Deliver to Austria

Premium delivery 7 - 10 business days

€17.95
(Includes tracking information)

Product Details

Publication date : Jan 29, 2021
Length: 298 pages
Edition : 1st
Language : English
ISBN-13 : 9781800565074
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€18.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€189.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€264.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 99.97
Vue.js 3 Cookbook
€36.99
Vue.js 3 By Example
€32.99
Building Vue.js Applications with GraphQL
€29.99
Total 99.97 Stars icon

Table of Contents

8 Chapters
Data Binding, Events, and Computed Properties Chevron down icon Chevron up icon
Components, Mixins, and Functional Components Chevron down icon Chevron up icon
Setting Up Our Chat App - AWS Amplify Environment and GraphQL Chevron down icon Chevron up icon
Creating Custom Application Components and Layouts Chevron down icon Chevron up icon
Creating the User Vuex Module, Pages, and Routes Chevron down icon Chevron up icon
Creating Chat and Message Vuex, Pages, and Routes Chevron down icon Chevron up icon
Transforming Your App into a PWA and Deploying to the Web Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Most Recent
Rating distribution
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.8
(11 Ratings)
5 star 45.5%
4 star 27.3%
3 star 9.1%
2 star 0%
1 star 18.2%
Filter icon Filter
Most Recent

Filter reviews by




usr Aug 09, 2022
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
Can't recommend this book at all. Even though it's only one and a half years old, most of the book is already outdated. Vue, Quasar AND graphql have updated since this book was written, so you'd have to migrate everything on your own, which is kind of hard for people who just wanted to learn these 3 frameworks by reading the book. Plus, it has not been going much into depth to begin with. A lot of "we do this and that" but very little "we do this because... ".The source code in the repo hasn't been updated since 2 years so the author does not seem to care about it anymore either.
Amazon Verified review Amazon
Casey Miller May 08, 2022
Full star icon Full star icon Full star icon Empty star icon Empty star icon 3
Book was well put together and got to point. Issue was AWS and Quasar have already updated.
Amazon Verified review Amazon
Ross Feb 21, 2022
Full star icon Empty star icon Empty star icon Empty star icon Empty star icon 1
The third chapter gives instructions for creating a graphQL api that the rest of the book builds on. Among other issues, these instructions rely heavily on the deprecated "@connection" directive. It may be possible for the reader to migrate these to the updated relationship mapping directives, however the author does not explain what the relationships between objects should be.tl;dr - after the second chapter, this book is out of date to the point of being useless.
Amazon Verified review Amazon
Alexander Pabel Feb 13, 2022
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
Dieses Buch ist ganz gut und man lernt viel neues zu Vuejs, Quasar und Amplify. Manche Kapitel hätte ich mir umfangreicher vorstellen können.
Amazon Verified review Amazon
Mohith Kumar Jul 15, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
If you are learning vue.js and GraphQL this book is a good buy. It does cover step-by-step recipes to build an app in Vue.jsThe bonus you will also explore AWS Amplify to host the GraphQL Server.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela