Template Slot Vue Js

Vue.js documentation: Using Named Slots. Named slots work similarly to single slots but instead allow you to distribute content to different regions within your child component template.

  • Template slot='i' template As you can see, I pass the update method to the slot component as a parameter, then I bind it to the evnet on the component in the slot in the parent of the DataProvider.
  • Practical use of Components and Mixins in Vue JS; 2.6.0+ If you use Vue version above 2.6.0, Vue introduces new unified slot api, which is v-slot. It replaces the slot and slot-scope attributes, which are deprecated, but have not been removed and are still documented here. You can refer to deprecated API here. # Slots (Default).
  • IMHO, it would be great to have 'template inheritance' as a part of a Vue core. When you have components inheritance (I mean JS part) and don't have template inheritance (just child vs parent template) - it shortly becomes a pain. In my case, I've extracted a few base components, for example, EditForm.
  • 我们可以看到子组件的 slot 标签多了 text 属性,以及:msg 属性。父组件实现插槽的部分多了一个 template 标签,以及 scope-slot 属性,其实在 Vue 2.5+ 版本,scoped-slot 可以作用在普通元素上。这些就是作用域插槽和普通插槽在写法上的差别。.

Do you find yourself wanting to extend a component's template? Vue provides several means of extracting and reusing the component logic and state, but templates are deemed single-use only.

In this article, I'll present a solution using the HTML template pre-processor Pug. It's a bit of a hack, but it does work!

If you're looking to extend a component's logic and state, you probably want to read my other article Extending Vue.js Components which covers composition functions, mixins, higher-order components, etc. Here we're talking about the template.

Case study: a survey app

Let's say you're trying to make a component-based survey app with Vue which looks like this:

Notice that each survey question has a different input type:

  1. Text input
  2. Select input
  3. Radio input

An obvious architecture would be to make each question into a separate component, which is what we're going to do. Well name these components:

  1. SurveyInputText
  2. SurveyInputSelect
  3. SurveyInputRadio

Base component

Let's first create a 'base' component called SurveyInput.vue. Notice the following about it:

  • The question prop is going to be common across each component. This captures the question text e.g. 'What is your name'.
  • We don't have any shared logic, but you could add it e.g. validation and error logic
  • I'm leaving a gap where the input element will need to go. This is where we'll extend the template as you'll shortly see.

SurveyInput.vue

State and logic reuse

Ignoring the template for a moment, what will be our strategy for extraction and reuse of the state/logic? What we choose here will not affect our ability to extend the component template, so feel free to use mixins, higher-order components, or even the new composition API. I'm going to keep it simple and use the extends component option.

Again, there's still a hanging question about how we get the base template into this subcomponent, as the Vue.extends API doesn't apply to templates!

SurveyInputText.vue

Never miss a new post!

Get our latest post in your inbox every Tuesday by subscribing to the Vue.js Developers Newsletter .

This subscription also includes Vue.js Developers promotional emails. You can opt-out at any time. View our privacy policy .

This form is protected by reCAPTCHA. The Google privacy policy and terms of service apply.

Conventional options for extending the template (and why they aren't ideal)

A reasonable Vue user would first consider the following design patterns for the template:

  • Props-driven template logic
  • Slots

But as I'll show, both have downsides for the use case we're considering, so there is a legitimate case for wanting to use the template-extension hack I'm going present.

Props-driven template logic

Rather than making separate components for each input type, you could create a single mega-component and then specify the template using conditional logic fed by a prop called question-type i.e.

SurveyInput.vue

Now you can declare your questions in the parent, Survey.vue, like this:

Survey.vue

The downside of this approach is that it doesn't scale well. Once you get, say, 3-4 different question types the template will become big and messy.

Slots

Another conventional approach that could work is to put a slot where the input belongs.

SurveyInput.vue

Using this approach the parent can provide the correct markup for each input like this:

Survey.vue

The downside to slots, though, is that you now have to organize the data model differently. Now, the parent owns the state of each input and would have to communicate it with each child component using props/events, adding complex logic and verbosity.

Survey.vue

Hopefully, I've convinced you that template logic and slots aren't ideal, so now let's look at how we can extend a component's template like we can with its state/logic.

To do this, we're going to need an HTML template pre-processor.

Pug HTML pre-processor

By using vue-loader and the lang property of the template in a single-file component, we can use HTML template pre-processing tools like Slim, Handlebars, etc.

My hack for extending Vue templates is to use the include and extends options provided by Pug (previously Jade).

First, add Pug to your project. Vue Loader should pick this up without any further config needed.

Now, let's convert our base component's template to Pug syntax:

Notice that we use block input to declare an 'outlet' where the subcomponent content will be distributed.

Important: the outlet is kind of like a slot but the important difference is that template processing occurs at compile-time, not at run-time as it would with slots.

Creating a base template

So here's where it gets slightly messy. If we want our child components to extend the template we first need to put it into its own file SurveyInput.pug:

SurveyInput.pug

Now we can include this file in our base component so it can still be used as a normal single-file component:

SurveyInput.vue

It's a shame to have to do that since it kind of defeats the purpose of 'single file' components. Probably someone could make a custom webpack loader to avoid having to do this.

Extending to a subcomponent

To extend the subcomponent's template from the base, you'll need to covert its template to Pug as well.

The subcomponents use the extends feature of Pug which includes the base component and outputs any custom content in the input block (again, similar to slots, but it happens at compile-time).

SurveyInputText.vue

Here's what the subcomponent's template would effectively look like after extending the base and being translated to a regular HTML Vue template:

Bring it all together

Using this strategy we can go ahead and create the other two subcomponents SurveyInputSelect and SurveyInputRadio. If we then use them in a project our main template might look like this:

Survey.vue

And here's how the rendered markup would look:

Happy hacking!

Base Example

There are situations when you want the template inside the slot to be able to access data from the child component that is responsible for rendering the slot content. This is particularly useful when you need freedom in creating custom templates that use the child component’s data properties. That is a typical use case for scoped slots.

Imagine a component that configures and prepares an external API to be used in another component, but is not tightly coupled with any specific template. Such a component could then be reused in multiple places rendering different templates but using the same base object with specific API.

We’ll create a component (GoogleMapLoader.vue) that:

  1. Initializes the Google Maps API
  2. Creates google and map objects
  3. Exposes those objects to the parent component in which the GoogleMapLoader is used

Below is an example of how this can be achieved. We will analyze the code piece-by-piece and see what is actually happening in the next section.

Let’s first establish our GoogleMapLoader.vue template:

Now, our script needs to pass some props to the component which allows us to set the Google Maps API and Map object:

This is just part of a working example, you can find the whole example in the Codesandbox below.

Real-World Example: Creating a Google Map Loader component

1. Create a component that initializes our map

Template Slot Vue Js

GoogleMapLoader.vue

In the template, we create a container for the map which will be used to mount the Map object extracted from the Google Maps API.

Next up, our script needs to receive props from the parent component which will allow us to set the Google Map. Those props consist of:

  • mapConfig: Google Maps config object
  • apiKey: Our personal api key required by Google Maps

Then, we set the initial values of google and map to null:

On mounted hook we instantiate a googleMapApi and Map objects from the GoogleMapsApi and we set the values of google and map to the created instances:

So far, so good. With all that done, we could continue adding the other objects to the map (Markers, Polylines, etc.) and use it as an ordinary map component.

But, we want to use our GoogleMapLoader component only as a loader that prepares the map — we don’t want to render anything on it.

To achieve that, we need to allow the parent component that will use our GoogleMapLoader to access this.google and this.map that are set inside the GoogleMapLoader component. That’s where scoped slots really shine. Scoped slots allow us to expose the properties set in a child component to the parent component. It may sound like Inception, but bear with me one more minute as we break that down further.

2. Create component that uses our initializer component.

TravelMap.vue

In the template, we render the GoogleMapLoader component and pass props that are required to initialize the map.

Our script tag will look like this:

Still no scoped slots, so let’s add one.

3. Expose google and map properties to the parent component by adding a scoped slot.

Finally, we can add a scoped slot that will do the job and allow us to access the child component props in the parent component. We do that by adding the <slot> tag in the child component and passing the props that we want to expose (using v-bind directive or :propName shorthand). It does not differ from passing the props down to the child component, but doing it in the <slot> tag will reverse the direction of data flow.

GoogleMapLoader.vue

Now, when we have the slot in the child component, we need to receive and consume the exposed props in the parent component.

4. Receive exposed props in the parent component using slot-scope attribute.

To receive the props in the parent component, we declare a template element and use the slot-scope attribute. This attribute has access to the object carrying all the props exposed from the child component. We can grab the whole object or we can de-structure that object and only what we need.

Let’s de-structure this thing to get what we need.

TravelMap.vue

Even though the google and map props do not exist in the TravelMap scope, the component has access to them and we can use them in the template.

Download

You might wonder why would we do things like that and what is the use of all that?

Vue

Scoped slots allow us to pass a template to the slot instead of a rendered element. It’s called a scoped slot because it will have access to certain child component data even though the template is rendered in the parent component scope. This gives us the freedom to fill the template with custom content from the parent component.

5. Create factory components for Markers and Polylines

Now when we have our map ready we will create two factory components that will be used to add elements to the TravelMap.

GoogleMapMarker.vue

GoogleMapLine.vue

Both of these receive google that we use to extract the required object (Marker or Polyline) as well as map which gives as a reference to the map on which we want to place our element.

Each component also expects an extra prop to create a corresponding element. In this case, we have marker and path, respectively.

On the mounted hook, we create an element (Marker/Polyline) and attach it to our map by passing the map property to the object constructor.

There’s still one more step to go…

Template Slot Vue Js Download

6. Add elements to map

Let’s use our factory components to add elements to our map. We must render the factory component and pass the google and map objects so data flows to the right places.

Template Slot Vue Json

We also need to provide the data that’s required by the element itself. In our case, that’s the marker object with the position of the marker and the path object with Polyline coordinates.

Here we go, integrating the data points directly into the template:

We need to import the required factory components in our script and set the data that will be passed to the markers and lines:

When To Avoid This Pattern

It might be tempting to create a very complex solution based on the example, but at some point we can get to the situation where this abstraction becomes an independent part of the code living in our codebase. If we get to that point it might be worth considering extraction to an add-on.

Vue

Wrapping Up

Vue Js Template Slot

That’s it. With all those bits and pieces created we can now re-use the GoogleMapLoader component as a base for all our maps by passing different templates to each one of them. Imagine that you need to create another map with different Markers or just Markers without Polylines. By using the above pattern it becomes very easy as we just need to pass different content to the GoogleMapLoader component.

This pattern is not strictly connected to Google Maps; it can be used with any library to set the base component and expose the library’s API that might be then used in the component that summoned the base component.

이 문서에 문제를 제보하거나 기여하고 싶은 부분이 있으십니까? Github에서 이 페이지를 수정하세요!