Categories
Vue 3

Vue 3 — Slots

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use slots to populate content with Vue 3.

Slots

We can add slots to let us distribute content to the location we want.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>click me</click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We created the click-button component with a slot inside so that we can add our own content between the tags.

It’ll render to the button with ‘click me’ as the content text.

We can also add HTML between the tags.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button><b>click me</b></click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the ‘click me’ text in bold since we have the b tag.

Other components can also be between the tags.

So we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button><hello-world></hello-world></click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>`
      });

      app.component("hello-world", {
        template: `
          <b>click me</b>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the hello-world component in between the click-button tags and we’ll see the same result as before.

If a component’s template didn’t contain the slot element, then any content in between the tags would be discarded.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>click me</click-button>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("click-button", {
        template: `
          <button></button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then ‘click me’ wouldn’t be displayed and we have an empty button.

Render Scope

We can access items from the child component from the parent if we have a slot.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button>
        delete {{item.name}}
      </click-button>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            item: {
              name: "this"
            }
          };
        }
      });

      app.component("click-button", {
        template: `
          <button>
            <slot></slot>
          </button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then we passed in the item.name string as the part of the content of the slot in the click-button component.

This is only for display. item is still in the parent’s scope.

Fallback Content

Fallback content can be added to a slot.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <click-button> </click-button>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            item: {
              name: "this"
            }
          };
        }
      });

      app.component("click-button", {
        template: `
          <button>
            <slot>submit</slot>
          </button>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

to add default content between the slots.

Since we have nothing between the click-button tags, we’ll see ‘submit’ displayed.

Conclusion

Slots are useful for letting us distribute content within a component.

Categories
Vue 3

Vue 3 — Provide and Inject

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at providing and injecting data with Vue 3.

Provide and Inject

To make passing data easier between deeply nested components, we can bypass props and use the provide property to expose some data.

And then we can use the inject property to get the data.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world" }
          };
        },
        provide: {
          author: "james"
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["author"],
        template: `
          <p>{{author}}</p>
        `,
        created() {
          console.log(this.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the blog-post and blog-post-statistics components.

In the blog-post component, we have the provide property with the author property.

This is the data that we’re providing to child components.

Therefore, in the blog-post-statistics component, we have the inject property with an array of the property names that are in provide that we want to get.

So author in blog-post-statistics would be author in the template or this.author in component methods.

However, this won’t work with Vue instance properties.

So if we have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world", author: "james" }
          };
        },
        provide: {
          post: this.post
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["post"],
        template: `
          <p>{{post.author}}</p>
        `,
        created() {
          console.log(this.post.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

Then nothing will be displayed.

To fix this, we got to pass in the property that has the value to provide and change provide to a function:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post></blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        data() {
          return {
            post: { title: "hello world", author: "james" }
          };
        },
        provide() {
          return {
            author: this.post.author
          };
        },
        template: `
          <div>
            {{ post.title }}
            <blog-post-statistics></blog-post-statistics>
          </div>
        `
      });

      app.component("blog-post-statistics", {
        inject: ["author"],
        template: `
          <p>{{author}}</p>
        `,
        created() {
          console.log(this.author);
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

Now the provide is a method of the blog-post component instead of a property.

And we return what we want to provide to child components.

Working with Reactivity

Changes with reactive properties in the parent won’t be reflected in child components when we update them.

This is a problem that can be fixed by making the reactive property a computed property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <counter></counter>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("counter", {
        data() {
          return {
            count: 0
          };
        },
        provide() {
          return { count: Vue.computed(() => this.count) };
        },
        template: `
          <div>
            <button @click='count++'>increment</button>
            <count-display></count-display>
          </div>
        `
      });

      app.component("count-display", {
        inject: ["count"],
        template: `
          <p>{{count.value}}</p>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have a counter component with a provide method that returns an object with the count property.

Its value is a computed property with this.count returned in the callback.

This way, the latest value will be propagated to the child component.

Then in the count-display component, we can get the value with the value property.

Conclusion

Provide and inject gives is a way to pass data to deeply nested components without using props.

Categories
Vue 3

Vue 3 — Named Slots and Slot Props

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use slots to populate content with Vue 3.

Named Slots

We can name our slots so that we can have multiple slots in one component.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <blog-post>
        <template v-slot:header>
          <h1>header</h1>
        </template>

        <template v-slot:default>
          <p>lorem ipsum.</p>
        </template>

        <template v-slot:footer>
          <p>footer</p>
        </template>
      </blog-post>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("blog-post", {
        template: `
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

The blog-post component has the header, main, and footer elements.

Inside each, they have their own slots.

We name the top slot with the name header .

And we named the bottom slot with the name footer .

The main tag has the slot with no name.

Then we can populate them with the template element.

The v-slot directive lets us populate the slots by passing in the slot name as the argument.

If a slot has no name, then we can refer to it with default .

Therefore, we get the h1 displayed on top.

‘lorem ipsum’ is displayed in the middle.

And ‘footer’ is displayed at the bottom.

Scoped Slots

Scoped slots let us make states from the child element available to the parent.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items>
        <template v-slot:default="slotProps">
          <b>{{ slotProps.item.name }}</b>
        </template>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the items component with the items that we want to render.

Instead of rendering them directly, we pass them into the slot with the v-bind directive so that we can change how they’re rendered in the parent.

The argument of v-bind will be the property name in the slotProps variable in the parent’s template.

The value will be the property value of the slotProps.item property.

Therefore, we can add the template tag in between the items tag.

The template tag has the v-slot:default directive with the slotProps value.

And we can use that to customize the rendering of the properties passed in from v-bind in items .

If there’s only one slot, then we can shorter the syntax for accessing the lone default slot.

For instance, we can write:

v-slot="slotProps"

instead of:

v-slot:default="slotProps"

So we can have:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <items>
        <template v-slot="slotProps">
          <b>{{ slotProps.item.name }}</b>
        </template>
      </items>
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("items", {
        template: `
          <ul>
            <li v-for="( item, index ) in items">
              <slot v-bind:item="item"></slot>
            </li>
          </ul>
        `,
        data() {
          return {
            items: [{ name: "apple" }, { name: "orange" }, { name: "grape" }]
          };
        }
      });

      app.mount("#app");
    </script>
  </body>
</html>

which is slightly shorter.

Conclusion

We can add named slots to our component and access data from the child in the parent with slot props.

Categories
Vue 3

Vue 3 — Custom Events

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to emit and handle custom events with Vue 3.

Custom Events

We can emit custom events with the this.$emit method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <component-a @increment-count="count++"></component-a>
      <p>{{count}}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            count: 0
          };
        }
      });

      app.component("component-a", {
        template: `
          <div>
            <button @click='this.$emit("increment-count")'>click      me</button>
          </div>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We emit the increment-count event with the from component-a .

Then we listen to the same event within the parent Vue instance’s template with:

<component-a @increment-count="count++"></component-a>

The case of the event names have to be the same for this to work.

Therefore, when we click on the ‘click me’ button, we can increase the count by 1.

Defining Custom Events

We can define custom events with the emits option of a component.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <component-a @increment-count="count++"></component-a>
      <p>{{count}}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            count: 0
          };
        }
      });

      app.component("component-a", {
        emits: ["increment-count"],
        template: `
          <div>
            <button @click='$emit("increment-count")'>click me</button>
          </div>`
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the emits option which has an array of event name strings that component-a can emit.

This serves as better documentation for our components since we can restrict the kinds of events that can be emitted.

Validate Emitted Events

We can add code to validate emitted events.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <component-a @increment-count="count += $event"></component-a>
      <p>{{count}}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            count: 0
          };
        }
      });

      app.component("component-a", {
        emits: {
          ["increment-count"](value) {
            return value === 2;
          }
        },
        template: `
          <div>
            <button @click='$emit("increment-count", 2)'>click me</button>
          </div>`
      });

app.mount("#app");
    </script>
  </body>
</html>

to make sure that we always pass 2 into the $emit method.

We have the emits property in our component-a object with the increment-count method.

It takes the value parameter, which is the value that we passed in as the 2nd argument.

In the method, we return the validation result as a boolean.

If it’s valid, we return true .

Otherwise, we return false .

v-model arguments

v-model arguments can also be validated.

We can check the prop value with the props property.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <component-a v-model:foo="bar"></component-a>
      <p>{{bar}}</p>
    </div>
    <script>
      const app = Vue.createApp({
        data() {
          return {
            bar: ""
          };
        }
      });

      app.component("component-a", {
        props: {
          foo: String
        },
        template: `
          <input
            type="text"
            :value="foo"
            @input="$emit('update:foo', $event.target.value)">
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

We have the props property, that has the key foo .

The value is String , so foo has to be a string.

Our input emits the update:foo event so we send the inputted value back to the parent component.

Then we can bind the parent’s property with the v-model:foo directive so that we can synchronize the value of foo with the value of the bar state.

Therefore, when we type into the input box, we’ll see the value that we entered displayed.

Conclusion

We can emit custom events by calling this.$emit with an event name and an optional 2nd argument with the value.

Then we can listen to it with the @ directive shorthand.

Categories
Vue 3

Vue 3 — v-model Modifiers and Components

Vue 3 is in beta and it’s subject to change.

Vue 3 is the up and coming version of Vue front end framework.

It builds on the popularity and ease of use of Vue 2.

In this article, we’ll look at how to use the Vue 3 v-model directive and create simple Vue 3 components.

Select Options

Select option values can be objects.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <select v-model="selected">
        <option :value="{ fruiit: 'apple' }">apple</option>
        <option :value="{ fruiit: 'orange' }">orange</option>
        <option :value="{ fruiit: 'grape' }">grape</option>
      </select>
      <p>{{ selected }}</p>
    </div>
    <script>
      const vm = Vue.createApp({
        data() {
          return { selected: {} };
        }
      }).mount("#app");
    </script>
  </body>
</html>

to create a select dropdown that has option values bound to objects.

:value accepts an object.

So when we select a value, we’ll see that selected would also be an object.

This is because we set v-model ‘s value to selected .

v-model Modifiers

v-model can take various modifiers.

.lazy

The .lazy modifier makes v-model sync with the Vue instance state after each change event.

By default, v-model syncs with the state after each input event is emitted.

We can use that by writing:

<input v-model.lazy="msg" />

.number

The .number modifier lets us convert whatever is entered to a number automatically.

For instance, we can write:

<input v-model.number="numApples" type="number" />

to convert numApples to a number automatically.

By default, an HTML input’s value is always a string, so this is a useful shorthand.

If the value can’t be parsed with parseFloat , then the original value is returned.

.trim

The .trim modifier automatically trims whitespace from user inputs.

We can use it by writing:

<input v-model.trim="msg" />

v-model with Components

v-model can be used with components as long as it emits an input event and takes in a value prop to populate the form control’s value.

This lets us make custom form control components easily.

Components Basics

We can create Vue components with the app.component method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://unpkg.com/vue@next"></script>
  </head>
  <body>
    <div id="app">
      <button-counter />
    </div>
    <script>
      const app = Vue.createApp({});

      app.component("button-counter", {
        data() {
          return {
            count: 0
          };
        },
        template: `
          <div>
            <button @click="count++">
              increment
            </button>
            <p>{{ count }}</p>
          </div>
        `
      });

      app.mount("#app");
    </script>
  </body>
</html>

to create a component and then use it in our app.

We called app.component to with the component name as the first argument.

We called our component 'button-counter' .

The 2nd argument has the component object with the template and logic.

data returns the initial state object.

And template has the template string with the elements we want to display.

Inside it, we increment the count when a button is clicked and display the count .

Then we use the button-counter component in our app by adding the HTML for it.

Now we should see the increment button and the count value update as we click it.

Conclusion

v-model works with select options and components.

We can create simple components with the app.component method.