I am a beginner of the vue.js. I can't understand scopedSlots and example in the guide.Can you give me a detailed answer,thanks very much!
I think it's best to illustrate with an example using plain JavaScript functions.
Think of the component like a function, eg
function todoList(todos) {
// render <ul>, loop todos, render <li> and todo.text
}
Now imagine that function is able to be passed an optional content rendering function named slot
function todoList(todos, slot) {
slot = slot || slotProps => {
// render slotProps.todo.text
}
// render <ul>
todos.forEach(todo => {
// render <li>
slot({ todo }) // call "slot" function
}
}
That function assigned to slot is the equivalent of
<slot v-bind:todo="todo">
<!-- Fallback content -->
{{ todo.text }}
</slot>
from the example.
So now when you call this function, you can optionally use something like
todoList(todos, slotProps => {
if (slotProps.todo.isCompleted) {
// render a checkmark, eg ✓
}
// render slotProps.todo.text
})
The function passed in here is the equivalent of
<template slot-scope="slotProps">
<!-- Define a custom template for todo items, using -->
<!-- `slotProps` to customize each todo. -->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}
</template>
in the example.
Related
Thank you in advance.
So I am fetching list of blog categories via API and rendering it in a list using v-for.
I also need to fetch the amount of blogs in every category and place them beside the category.
But the issue is I am calling a method that calls the api.
<li v-for="item in sidebar" :key="item.identifier">
<nuxt-link
tag="a"
:to="{
name: 'blog-page',
query: { category: item.identifier }
}"
>{{ $localize(item.translations).title }}
{{ getBlogCount(item.identifier) }}
</nuxt-link>
</li>
You know what it shows already example is Animals [Object Promise]
methods: {
async getBlogCount(identifier) {
axios
.get(
"https://example.com/posts?fields=created_at&filter[category.category_id.identifier]=" +
identifier +
"&meta=*"
)
.then(count => {
return count.data.meta.result_count;
});
}
}
What is the best way to handle this kinda thing?
You better call async methods in mounted or created hooks, and set the result to data, and then, use that data in template.
I'd suggest handling this in Script, instead of HTML Template.
What you can do is, depending on when the sidebar is initialized (maybe in the mounted hook), call getBlogCount method to fetch blog counts for each item in sidebar and store that may be in an array or object (or as a separate key-value pair to that same sidebar item object) and then use that data structure to display count values in the template.
Assuming the sidebar is populated in mounted hook and that it's an array of objects, you can do the following:
<template>
<li v-for="item in sidebar" :key="item.identifier">
<nuxt-link
tag="a"
:to="{
name: 'blog-page',
query: { category: item.identifier }
}"
>{{ $localize(item.translations).title }}
{{ item.blogCount }}
</nuxt-link>
</li>
</template>
<script>
mounted () {
// after the sidebar is populated
this.sidebar = this.sidebar.map(async item => {
item.blogCount = await this.getBlogCount(item.identifier)
return item
})
}
</script>
Hope this helps you out
I am using Vue and am trying to make live search. But on updating the content of search, it doesn't get updated.
Data do get update in array, when checked in dev tools. But DOM don't get updated.
template
<div class="dropdown">
<input type="text" v-model="input" placeholder="Search" #keyup="searching" data-toggle="dropdown">
<span class="caret"></span>
<ul class="dropdown-menu">
<li v-for="(data,index) in availSearchData" :key="index">
{{data.name}}
</li>
</ul>
</div>
method
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
res.data.forEach(doc => {
this.availSearchData.push(doc);
});
});
}
}
I don't know where I am doing wrong.
Please help out if possible.
To add an item to the back of an array and get it to be reactive in Vue, below is what worked for me:
this.$set(this.items,
this.items.length,
JSON.parse(JSON.stringify(this.item))
);
The this.$set is Vue's inbuilt array manipulation function that guarantees reactivity.
The this.items is the array, this.items.length (NOTE: it is items.length NOT items.length - 1) is to push a new index to the back of the array and finally, JSON.parse(JSON.stringify(this.item)) is to clone the this.item into a new object before pushing into the array. The cloning part may not be applicable to you and I used this in variables because all the variables are declared in my data() function.
Use a computed property in your component and use that for parsing the template like this
<li v-for="(data,index) in availSearch" :key="index">
{{data.name}}
</li>
and computed property will be then
availSearch() {
return this.availSearchData;
},
so this computed property always return the array if it is updated.
Also if your response is the array that you want to use exactly, try this
searching() {
if (this.input) {
let url = this.domain + "search";
axios
.get(url, {
params: {
table: this.table,
data: this.input
}
})
.then(res => {
this.availSearchData = [];
Vue.set(this, 'availSearchData', res.data);
});
}
}
Possible explanations for this might be:
You don't declare the property in the component and thus normal
reactivity doesn't work.
You are using index as the key in your array. This might confuse the
reactivity system, so it does not necessarily know if the item
changed. Try using the name of the item as the key instead.
Try calling your function from mounted hook. I think the problem is that you are trying to show data when the DOM is not rendered yet. By calling your function in mounted you get data back after DOM has been rendered.
mounted() {
this.searching();
}
from Vue website "mounted: Called after the instance has been mounted, where el is replaced by the newly created vm.$el. If the root instance is mounted to an in-document element, vm.$el will also be in-document when mounted is called."
I have read this post which goes in depth about renderless components:
https://adamwathan.me/renderless-components-in-vuejs/
A renderless component would pretty much look like this:
export default {
render() {
return this.$scopedSlots.default({})
},
}
Now I would like to use this renderless component but also add a click listener to whatever is being passed into the slot.
In my case it would be a button. My renderless component would simply wrap a button and add a click listener to it, which in turn performs an AJAX request.
How would I go about adding a click listener to the element that is being passed into the slot?
Assuming you want to bind the click handler within the renderless component, I think from this post that you need to clone the vnode passed in to renderless, in order to enhance it's properties.
See createElements Arguments, the second arg is the object to enhance
A data object corresponding to the attributes you would use in a template. Optional.
console.clear()
Vue.component('renderless', {
render(createElement) {
var vNode = this.$scopedSlots.default()[0]
var children = vNode.children || vNode.text
const clone = createElement(
vNode.tag,
{
...vNode.data,
on: { click: () => alert('clicked') }
},
children
)
return clone
},
});
new Vue({}).$mount('#app');
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<renderless>
<button type="button" slot-scope="{props}">Click me</button>
</renderless>
</div>
Here's one way to go about this.
Your renderless component wrapper would consist of a single action (i.e. the function to issue the AJAX request) prop.
Vue.component('renderless-action-wrapper', {
props: ['action'],
render() {
return this.$scopedSlots.default({
action: this.action,
});
},
});
Then another component which uses the aforementioned wrapper would enclose a customisable slot with a #click handler, which invokes the action that is passed in when triggered.
Vue.component('clickable', {
props: ['action'],
template: `
<renderless-action-wrapper :action="action">
<span slot-scope="{ url, action }">
<span #click="action()">
<slot name="action"></slot>
</span>
</span>
</renderless-action-wrapper>
`,
});
Finally, wire up the specialised version of the wrapper.
<clickable :action="doAjaxRequest">
<button type="button" slot="action">Button</button>
</clickable>
Here's a live example of the above suggestion you can play around with.
I need help regarding how i can pass a function in *ngFor loop or any another suggestion ??
I have todo-list Component and a child todo-item component - I want to pass a function which will return a value- Please check the code below:
-- Todo List Component
`<div class="list-group">
<app-todo-item
*ngFor="let todo of todos; let i = index"
[todoItem]="todo"
[index]="i"></app-todo-item>
</div>`
-- Todo Item Component
`
<a
[routerLink]="index"
[routerLinkActive]="'list-group-item-warning'"
class="list-group-item">
{{ todo.name }}
<span class="badge badge-pill"
[ngClass]="{'badge-success': todo.status === 'active', 'badge-warning': todo.status === 'inactive'}">{{ todo.status }}</span>
<div class="option-group float-right">
<span class="badge badge-dark">2 days ago myFunction(date)</span>
</div>
</a>
`
Please let me know what possible way i can achieve this.
Thanks in advance
are you trying to pass the function to app-todo-item component?
if so you could use #Output from angular core
// import it inside your component
import { Output, EventEmitter } from '#angular/core'
// declare the function callback you want to recieve
#Output() callback = new EventEmitter();
// to make the callback send data back call, if you want to pass multiple fields put it all inside an object
this.callback.emit(datatobepassed)
// to make use of it do; you need to put $event or else function recieves undefined
<app-todo-item (callback)=”yourfunction($event)”>
// in your parent component
yourfunction(data: any)
{
// stuff
}
hope that helps
I'm new to Vue and using Vue 2.2.1. I am wondering if it's possible to create a reusable component that can have its layout defined by its parent. For example, consider the following pseudo code:
// Parent template
<template>
<ul>
<li v-for="item in items">
<item-component :id="item.id">
<h1><item-title /></h1>
<p>
<item-description />
</p>
</item-component>
</li>
</ul>
</template>
// Child definition
<script>
export default {
data() {
return {
title: '',
description: ''
}
}
create() {
// do some async fetch
fetch(this.id)
.then((result) {
this.$data.title = result.title
this.$data.description = result.description
})
}
}
</script>
So, the use case is that the child component is responsible for the fetching of the data by id, but the parent is responsible for laying out the data. This way, I can keep the fetch logic in one place, but reformat the data however I want in various places.
Not sure if this is possible or not. I suppose I can extract the child's fetching functionality out into a mixin, but then I'd have to create a new component for each layout variation. What is the recommended way to handle this in Vue?
In general, when you want the parent to include content in the child, the means to do it is via a slot. Inside, a typical slot, however, the scope is the parent's scope, meaning it does not have access to data inside the child.
In your case, you would want to use a scoped slot, which is where the child is able to pass some information back to the parent to use.
// Parent template
<template>
<ul>
<li v-for="item in items">
<item-component :id="item.id">
<template scope="props">
<h1>{{props.title}}</h1>
<p>
{{props.description}}
</p>
</template>
</item-component>
</li>
</ul>
</template>
// Child definition
<script>
export default {
template:"<div><slot :title='title' :description='description'></slot></div>",
data() {
return {
title: '',
description: ''
}
}
create() {
// do some async fetch
fetch(this.id)
.then((result) {
this.$data.title = result.title
this.$data.description = result.description
})
}
}
</script>