Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
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

Arrow left icon
Profile Icon Heitor Ramon Ribeiro
Arrow right icon
Free Trial
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.8 (11 Ratings)
Paperback Jan 2021 298 pages 1st Edition
eBook
S$29.99 S$42.99
Paperback
S$52.99
Subscription
Free Trial
Arrow left icon
Profile Icon Heitor Ramon Ribeiro
Arrow right icon
Free Trial
Full star icon Full star icon Full star icon Half star icon Empty star icon 3.8 (11 Ratings)
Paperback Jan 2021 298 pages 1st Edition
eBook
S$29.99 S$42.99
Paperback
S$52.99
Subscription
Free Trial
eBook
S$29.99 S$42.99
Paperback
S$52.99
Subscription
Free Trial

What do you get with a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing
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

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 a Packt Subscription?

Free for first 7 days. $19.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing

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
$19.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
$199.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 S$6 each
Feature tick icon Exclusive print discounts
$279.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 S$6 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total S$ 179.97
Vue.js 3 Cookbook
S$66.99
Vue.js 3 By Example
S$59.99
Building Vue.js Applications with GraphQL
S$52.99
Total S$ 179.97 Stars icon
Banner background image

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

Top Reviews
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
Top Reviews

Filter reviews by




Nader Dabit Feb 10, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book is a fantastic deep dive into building an end to end application on AWS. I really like the fact that he dove deep into many topic areas, showing how to tie everything together to build something that is a real-world use case. The information in this book can also be used in many other areas so the knowledge is very transferable to other scenarios and use cases.
Amazon Verified review Amazon
Jeff Galbraith Mar 13, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
As a core team member of Quasar Framework, I found this book very informative and easy to read. It's well explained and will shed a lot of light on the mysteries of integrating GraphQL into your daily JavaScript activities.
Amazon Verified review Amazon
Essence Feb 04, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
This book is spot on. It goes into great detail about building applications in Vue and GraphQL. I would highly recommend this to any engineer interested in working with this technology. Excellent read!
Amazon Verified review Amazon
Brandon Galli Feb 04, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
As far as the front-end goes, I come from an Angular background. Compared to the Angular books I have read, or software books in general, this is one of the few books that I find superbly well organized and written.I could not put the book down at night to go to sleep, and during the day I can't wait till I come back from work to pour over the book. Part of my excitement might be Vue.js itself since I know how complex things were --relatively speaking-- in Angular.The best aspect of the book is how the authors discuss Vue.js at a "steady phase" that keeps your interest: not overly verbose to bore the heck out of you, and not too terse where you loose track of the material/idea the authors are trying to convey.I only wish more authors follow this style of writing.
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 included in a Packt subscription? Chevron down icon Chevron up icon

A subscription provides you with full access to view all Packt and licnesed content online, this includes exclusive access to Early Access titles. Depending on the tier chosen you can also earn credits and discounts to use for owning content

How can I cancel my subscription? Chevron down icon Chevron up icon

To cancel your subscription with us simply go to the account page - found in the top right of the page or at https://subscription.packtpub.com/my-account/subscription - From here you will see the ‘cancel subscription’ button in the grey box with your subscription information in.

What are credits? Chevron down icon Chevron up icon

Credits can be earned from reading 40 section of any title within the payment cycle - a month starting from the day of subscription payment. You also earn a Credit every month if you subscribe to our annual or 18 month plans. Credits can be used to buy books DRM free, the same way that you would pay for a book. Your credits can be found in the subscription homepage - subscription.packtpub.com - clicking on ‘the my’ library dropdown and selecting ‘credits’.

What happens if an Early Access Course is cancelled? Chevron down icon Chevron up icon

Projects are rarely cancelled, but sometimes it's unavoidable. If an Early Access course is cancelled or excessively delayed, you can exchange your purchase for another course. For further details, please contact us here.

Where can I send feedback about an Early Access title? Chevron down icon Chevron up icon

If you have any feedback about the product you're reading, or Early Access in general, then please fill out a contact form here and we'll make sure the feedback gets to the right team. 

Can I download the code files for Early Access titles? Chevron down icon Chevron up icon

We try to ensure that all books in Early Access have code available to use, download, and fork on GitHub. This helps us be more agile in the development of the book, and helps keep the often changing code base of new versions and new technologies as up to date as possible. Unfortunately, however, there will be rare cases when it is not possible for us to have downloadable code samples available until publication.

When we publish the book, the code files will also be available to download from the Packt website.

How accurate is the publication date? Chevron down icon Chevron up icon

The publication date is as accurate as we can be at any point in the project. Unfortunately, delays can happen. Often those delays are out of our control, such as changes to the technology code base or delays in the tech release. We do our best to give you an accurate estimate of the publication date at any given time, and as more chapters are delivered, the more accurate the delivery date will become.

How will I know when new chapters are ready? Chevron down icon Chevron up icon

We'll let you know every time there has been an update to a course that you've bought in Early Access. You'll get an email to let you know there has been a new chapter, or a change to a previous chapter. The new chapters are automatically added to your account, so you can also check back there any time you're ready and download or read them online.

I am a Packt subscriber, do I get Early Access? Chevron down icon Chevron up icon

Yes, all Early Access content is fully available through your subscription. You will need to have a paid for or active trial subscription in order to access all titles.

How is Early Access delivered? Chevron down icon Chevron up icon

Early Access is currently only available as a PDF or through our online reader. As we make changes or add new chapters, the files in your Packt account will be updated so you can download them again or view them online immediately.

How do I buy Early Access content? Chevron down icon Chevron up icon

Early Access is a way of us getting our content to you quicker, but the method of buying the Early Access course is still the same. Just find the course you want to buy, go through the check-out steps, and you’ll get a confirmation email from us with information and a link to the relevant Early Access courses.

What is Early Access? Chevron down icon Chevron up icon

Keeping up to date with the latest technology is difficult; new versions, new frameworks, new techniques. This feature gives you a head-start to our content, as it's being created. With Early Access you'll receive each chapter as it's written, and get regular updates throughout the product's development, as well as the final course as soon as it's ready.We created Early Access as a means of giving you the information you need, as soon as it's available. As we go through the process of developing a course, 99% of it can be ready but we can't publish until that last 1% falls in to place. Early Access helps to unlock the potential of our content early, to help you start your learning when you need it most. You not only get access to every chapter as it's delivered, edited, and updated, but you'll also get the finalized, DRM-free product to download in any format you want when it's published. As a member of Packt, you'll also be eligible for our exclusive offers, including a free course every day, and discounts on new and popular titles.