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:
- Text input
- Select input
- 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:
SurveyInputText
SurveyInputSelect
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:
- Initializes the Google Maps API
- Creates
google
andmap
objects - 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
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.
You might wonder why would we do things like that and what is the use of all that?
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.
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.