Secured Communication with Vue.js Components

You don't need to look far before noticing component-driven architecture in modern web applications nowadays. Development needs have changed in a short space of time with the web going from a simple document viewer to hosting complex applications with significantly large code bases. Therefore, the ability to create reusable components makes our lives as front-end developers much easier as we can encapsulate core functionality into singular blocks, reducing overall complexity, allowing for better separation of concerns, collaboration, and scalability.

In this chapter, we'll be taking the preceding concepts and applying them to our Vue applications. By the end of this chapter, you will have achieved:

  • The ability to create your own Vue components
  • A greater understanding of Single File Components
  • The ability to create styles specific to each component
  • The ability to register components both locally and globally, and an understanding of why to select one over the other
  • The ability to communicate between parent and child components using props
  • The ability to communicate across the application using a global Event Bus
  • The ability to use slots to make your components more flexible

Let's start off by looking atYour first Vue component.

Your first Vue component

It turns out that we've been using components all along inside of our Vue applications! Using thewebpack-simpletemplate, we have support forSingle File Components(SFC), which is essentially just a template, script, and style tag with a.vueextension:

# Create a new Vue project


$ vue init webpack-simple vue-component-1




# Navigate to directory


$ cd vue-component-1




# Install dependencies


$ npm install




# Run application


$ npm run dev

As we're using the Vetur extension for Visual Studio Code, we're able to typescaffoldand hitTab, this then creates an SFC that can be used inside of our project. If we overwriteApp.vuewith an empty component, by our current definition it will look as follows:

That's it! Sort of. We've still got to add some functionality to our component, and if we were creating a new file (that is, not using the defaultApp.vuecomponent), register it somewhere to be used. Let's see this in action by creating a new file undersrc/components/FancyButton.vue:

<
template
>


<
button
>


  {{buttonText}}


<
/button
>


<
/template
>




<
script
>


export default {


 data() {


  return {


   buttonText: 'Hello World!'


  }


 }


}


<
/script
>




<
style
>


 button {


  border: 1px solid black;


  padding: 10px;


 }


<
/style
>

OurFancyButtoncomponent is simply a button that says'Hello World!'and has a tiny bit of styling. Immediately, we need to be thinking about things that we can do to make this more scalable:

  • Allow for an input on this component to change the button text
  • As we're styling the button element (or even if we had added classes), we need a way to stop the styles leaking out into the rest of our application
  • Register this component so that it can be used globally throughout the application
  • Register this component so that it can be used locally within a component
  • And many more!

Let's start with the easiest one, registering the component so it can be used within our application.

Registering components globally

We can create components and register them globally with the following interface:Vue.component(name: string, options: Object<VueInstance>). Although it isn't required, when naming our components it's important to adhere to the naming conventions set by the W3C Custom Elements specification (https://www.w3.org/TR/custom-elements/#valid-custom-element-name), that is,all-lowercase and it must contain a hyphen.

Inside of ourmain.jsfile, let's register ourFancyButtoncomponent by first importing it from the appropriate path:

import FancyButton from './components/FancyButton.vue';

Afterwards, we can register the component usingVue.component, which can be seen in bold, making the resulting code inside ofmain.jslike so:

import Vue from 'vue';


import App from './App.vue';


import FancyButton from './components/FancyButton.vue';




Vue.component('fancy-button', FancyButton);




new Vue({


  el: '#app',


  render: h =
>
 h(App)


});

Tada! Our component has now been registered globally. Now... how do we use this inside of ourApp.vuecomponent? Well, remember the tag that we specified? We just add that to thetemplatelike so:

<
template
>


<
fancy-button/
>


<
/template
>

Here's the results of our hard work (zoomed in to 500%):

Scoped styles

Great! What happens if we add another button element? As we styled thebuttonelement directly with CSS:

<
template
>


<
div
>


<
fancy-button
>
<
/fancy-button
>


<
button
>
I'm another button!
<
/button
>


<
/div
>


<
/template
>

If we head over to our browser we can see each of the buttons that we have created:

Uh oh! This other button isn't afancy-button, so why is it getting the styles? Thankfully, stopping the styles from leaking outside of the component is simple, all we need to do is add thescopedattribute to thestyletag:

<
style scoped
>


 button {


 border: 1px solid black;


 padding: 10px;


 }


<
/style
>

The scoped attribute isn't part of Vue by default, this comes from our Webpackvue-loader. You'll notice that after adding this, the button styles are specific to ourfancy-buttoncomponent only. If we look at the differences between the two buttons in the following screenshot, we can see that one is merely a button and the other is styling a button with a randomly generated data attribute. This stops the browser from applying the styles to both button elements in this scenario.

When using scoped CSS within Vue, keep in mind that rules created within the component won't be accessible globally across the application:

Registering a component locally

We can also register our components locally within our application. This can be done by adding it specifically to our Vue instance, for example, let's comment out the global registration withinmain.jsand then navigate toApp.vue:

// Vue.component('fancy-button', FancyButton);

Before adding any code into our app component, notice that our button has disappeared now that we're no longer globally registering it. To register this locally, we'll need to first import the component similar to how we did before and then add this to acomponentobject within the instance:

<
template
>


<
div
>


<
fancy-button
>
<
/fancy-button
>


<
button
>
I'm another button!
<
/button
>


<
/div
>


<
/template
>




<
script
>


import FancyButton from './components/FancyButton.vue';




export default {


 components: {


 FancyButton


 }


}


<
/script
>




<
style
>




<
/style
>

Our button has now appeared on screen again. When deciding where to register your components, think about how often they may need to be used throughout the project.

Component communication

We've now got the ability to create reusable components that allow us to encapsulate functionality within our project. In order to make these components usable, we'll need to give them the ability to communicate with oneanother. The first thing we'll be looking at is one way communication with component properties (referred to as "props").

The point of component communication is to keep our features distributed, loosely coupled, and in turn make our application easier to scale. To enforce loose coupling, you should not attempt to reference parent component(s) data within the child component and it should be passed usingpropsonly. Let's take a look at making a property on ourFancyButtonthat changes thebuttontext:

<
template
>


<
button
>


  {{buttonText}}


<
/button
>


<
/template
>




<
script
>


export default {


 props: ['buttonText'],


}


<
/script
>




<
style scoped
>


 button {


 border: 1px solid black;


 padding: 10px;


 }


<
/style
>

Notice how we're able to bind to thebuttonTextvalue inside of our template as we've created ourselves apropsarray that contains string or object values for each component property. Setting this can be done with kebab case as an attribute on the component itself, this is required as HTML is case-insensitive:

<
template
>


<
fancy-button button-text="I'm set using props!"
>
<
/fancy-button
>


<
/template
>

This gives us the following result:

Configuring property values

We can further configure our property values by instead setting them as an object. This allows us to define things such as defaults, types, validators, and so on. Let's do this with ourbuttonTextproperty:

export default {


 props: {


  buttonText: {


   type: String,


   default: "Fancy Button!",


   required: true,


   validator: value =
>
 value.length 
>
 3


  }


 },


}

Firstly, we're ensuring that we can only pass String types into this property. We can also check against other types, such as:

  • Array
  • Boolean
  • Function
  • Number
  • Object
  • String
  • Symbol

According to web component good practices, sending primitive values to props is a good practice.

Under the hood, this is running theinstanceofoperator against the property so it could also run a check against constructor types, as seen in the following screenshot:

At the same time, we can also check for multiple types using the array syntax:

export default {


 props: {


  buttonText: {


   type: [String, Number, Cat],


  }


 },


}

Next, we're setting the default text to beFancyButton!, which means that by default, if this property wasn't set, it'd have that value. We've also set required equal to true, meaning that any time we create aFancyButtonwe have to include thebuttonTextproperty.

This is currently a contradiction in terms (that is, default value and required), but there are times where you'd want a default value where the property isn't required. Finally, we're adding a validation function to this to specify that any time we set this property, it has to have a string length greater than three.

How do we know whether a property validation has failed? In development mode, we can check our development console and we should have a corresponding error. For example, if we forget to add thebuttonTextproperty on our component:

results matching ""

    No results matching ""