I'm really stuck on this one.I have created a Vue (2.0) component that is made up of child components, it's all being Webpacked etc. For example, this is the parent:
<div>
<h1>This is just a title for lulz</h1>
<rowcomponent v-for="row in rows" :row-data="row"></rowcomponent>
</div>
which has a prop of rows passed to it which looks something like this:
rows: [{ sometext: "Foo"} , { sometext: "Bar" }]
So my row component looks like this:
<div>{{ this.sometext }} <button #click="deleteRow">Delete Row</button></div>
And I feel like it should be really easy to set a method on the rowcomponent that is something like this:
deleteRow() {
this.delete();
}
Do I need to $emit something to the parent with the index of the row in it to remove it? It's driving me crazy. All the examples seem to suggest it would be easy to do, but not in the case where you have child components that want to delete themselves.
Yes, the child component cannot delete itself. The reason is because the original data for rows is held in the parent component.
If the child component is allowed to delete itself, then there will be a mismatch between rows data on parent and the DOM view that got rendered.
Here is a simple jsFiddle for reference: https://jsfiddle.net/mani04/4kyzkgLu/
As you can see, there is a parent component that holds the data array, and child component that sends events via $emit to delete itself. The parent listens for delete event using v-on as follows:
<row-component
v-for="(row, index) in rows"
:row-data="row"
v-on:delete-row="deleteThisRow(index)">
</row-component>
The index property provided by v-for can be used to call the deleteThisRow method, when the delete-row event is received from the child component.
Related
I'm struggling to understand how I should be testing my parent/child components using the [Vue Testing Library][1].
I have a parent component called SavedCards which displays a child component (called SavedCard) for every saved card in an array i.e something like:
<script>
async function deleteCard(token: string) {
applicationStore.deleteCard(token);
}
</script>
<template>
<saved-card v-for="savedCard in getSavedCards"
#delete-card="deleteCard"
:savedCard="savedCard">
</saved-card>
</template>
The child component displays a Delete button which when clicked emits the delete-card event which (in the Parent component) calls the deleteCard function - this then results in the card being delete and removed from the list.
I would like to write a test for this but I cannot workout how!
When the test (on the parent component) runs the DOM elements of the child aren't there i.e I get something like this:
<card
class="saved-card"
data-v-2778b5d8=""
style="cursor: pointer;"
/>
So how do I get to that delete-card event in my test of the parent and make sure that after it is clicked the card disappears from view?
Can anyone offer up any advice as to how (using the Vue Testing Library) I should be writing this test?
[1]: https://testing-library.com/docs/
How can I update data object of parent whenever changes happen inside v-for. I have a child component that I use inside parent component.
ParentComponent.vue
<template>
....
....
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i" // currentPage doesn't update.
:page="i"
/>
<q-header>
{{currentPage}} // always displays default value:1
</q-header>
</template>
<script>
data () {
return {
pageCount: 10,
currentPage: 1,
}
},
How can I update currentPage of data object whenever i changes inside v-for. I have tried using watch without much luck. I don't have access to child component and can't modify it.
Much appreciated!
There is some slight confusion with how v-for is working on the child-component here. Writing currentPage="i" as a property (which should actually be v-bind:currentPage in order for the i to be interpreted as JS) will simply declare the attribute on each child-component
How can I update currentPage of data object whenever i changes inside v-for
i doesn't "change" in the traditional context of running a for loop inside of a normal JavaScript application. In Vue, your rendering logic and application logic are separate, and rightly so, because running logic as part of the rendering doesn't really make sense.
For example, let's look at how your app will render the child-component:
<!-- Vue output -->
<child-component ... currentPage="1" />
<child-component ... currentPage="2" />
<child-component ... currentPage="3" />
So let's look at separating the rendering logic from the application logic.
I realise you don't have access to child-component, but based on the context I will assume it is some kind of tabbing functionality (based on you trying to set a value for the "current page" - feel free to be more specific and I can update my answer).
We need to bridge that gap between the rendering logic and the application logic and we can do that by using events:
<child-component
v-for="i in count"
:ref="`childComponent-${i}`" // ref should be unique so add the i as part of it
:key="i"
:page="i"
v-on:click="currentPage = i" // when the user clicks this child component, the current page will be updated
/>
You may have to utilise a different event other than click but I hope this gets you closer to what you are trying to achieve. For the value of currentPage to update there has to be some kind of user input, so just find out which event makes the most sense. Maybe the child-component library you are using has custom events that are more appropriate.
you should look into Custom Events.
https://v2.vuejs.org/v2/guide/components-custom-events.html
Idea is, that whenever there is some update of your desire in child component, you can execute this.$emit(“change”), which will throw an event.
On parent side you can catch this event by #change=“myMethod” as one of the attributes.
methods: {
myMethod() {
console.log("Testing")
}
}
<child-component
v-for="i in count"
ref="childComponent"
:key="i"
currentPage="i"
:page="i"
#change=“myMethod”
/>
Let me know if that helped.
I'm trying to use vuejs to display a list of instances of a child component.
The child component has input fields that a user will fill in.
The parent will retrieve the array of data to fill in the child components (If any exists), but since they're input fields the child component will be making changes to the value (Which generates errors in the console, as child components aren't supposed to change values passed from the parent).
I could just be lazy and just do everything at the parent level (i.e. use a v-for over the retrieved array and construct the list of elements and inputs directly in the parent and not use a child component at all), but I understand that it's not really the vuejs way.
I'm not very familiar with child components, but I think if it was just static data I could just declare props in the child component, and fill it from the parent.
However what I kind to need to do is fill the child component from the parent, but also allow changes from within the child component.
Could someone please describe the correct way to achieve this?
Thanks
You can use inputs on child components. The pattern is like this (edit it's the same pattern for an array of strings or an array of objects that each have a string property as shown here):
data: function() {
return {
objects: [ { someString: '' }, { someString: '' } ]
}
}
<the-child-component v-for="(object, i) in objects" :key="i"
v-model="object.someString"
></the-child-component>
Then, in the child component:
<template>
<div>
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
/>
</div>
</template>
export default {
name: 'the-child-component',
props: ['value'],
}
See it described here: https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components
I've just wanted to know if it is possible to find a DOM element by the key attribute of vue?
I'm currently working with a list. I'm displaying it via v-for directive on a div. I'm binding the key with the index of the elements.
v-for="(apk, index) in project.apks" v-bind:key="index"
It would really help me if i could compute something for each of these elements as soon as they are fetch from my server and displayed. It's just parsing a file and looking for keyword, and accordingly choosing a css class for the items.
The problem is I dont know how to call a method for each of these elements as soon as they are added to the DOM. They are a lot of html events but i couldnt find one representing the object beeing inserted to dom :(
The purpose of key is not selecting element. Even if it can be done, don't do it.
The proper way to do it is by using ref.
for example, add ref attribute to your html like this
v-for="(apk, index) in project.apks" v-bind:key="index" :ref="'sample-ref-'+index"
and then in your methods, you can get the DOM using this.$refs['sample-ref-0'],this.$refs['sample-ref-1'] and so on.
Hope this helps.
I found that if you give the 'ref' the same name in a v-for, like this:
<div v-for="data in list" :key="data.id" ref="bar"></div>
Then you will find they just store in an array called 'bar' and you can visit them by:
this.$refs['bar'][index]
something like this could allow you to find a component by key :
this.$children.forEach(child=>{
print("child.$vnode.key")
})
also use mounted , as it gets called when the component is added to the dom:
mounted:function(){
this.$el.querySelector("#ele1")...
}
The problem is I dont know how to call a method for each of these elements as soon as they are added to the DOM. They are a lot of html events but i couldnt find one representing the object beeing inserted to dom :(
You can create a new component with your v-for and just call the created() hook.
Example
/* On your main function */
<div v-for="apk in project.apks">
<apk :data="apk"></apk>
</div>
/* On your 'apk' component */
export default {
props: [ "data" ],
created() {
console.log("Created !");
}
}
I have a parent component with the following line
<router-view :product-id="productId" :data-source="attributes"></router-view>
depending on the context it renders one of the two components defined in the router config
path: 'parent',
component: Parent,
children:
[
{
path: 'edit',
component: Edit,
children:
[
{
path: 'attribute/:id',
component: Attribute,
}
]
},
{
path: 'grid',
component: Grid,
}
]
The thing is that the product-id and data-source props are available only in the Edit and Grid components. I'd like to have them available in the Attribute component as well as the Edit component is just a background with some static text (common for many components).
As a workaround I've created a propertyBag prop in the Edit component that passes an object down. That's the way I use it in the parent component
<router-view :property-bag="{ productId:productId, dataSource:dataSource, ...
and the Edit component
<router-view :property-bag="propertyBag"></router-view>
Is there a simpler way to achieve it ?
Vue $attrs is the new way to propagate props
From the Docs:
vm.$attrs
Contains parent-scope attribute bindings (except for class and style) that are not recognized (and extracted) as props. When a component doesn’t have any declared props, this essentially contains all parent-scope bindings (except for class and style), and can be passed down to an inner component via v-bind="$attrs" - useful when creating higher-order components.
For more information, see Vue.js API Reference - $attrs
Have you looked at vuex. It's really quite easy to use and allows you to store data for your entire app in a single data store. This means you don't have to keep passing data through props, you can just access variables set in the store.
Here is a link to vuex docs (What is Vuex)
https://vuex.vuejs.org
You have to declare the props and bind them to pass them to the child.
Have a look at https://v2.vuejs.org/v2/api/#v-bind for available options
specifically, this may be of interest
<!-- binding an object of attributes -->
<div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>
<!-- but you can also... -->
<div v-bind="allProps"></div>
This means you can pass down the object, and have the child parse the appropriate props. This means that the child has to have the props defined in order to catch them. So what you may be able to do is, in the case of the parent, have :propBag="propBag" and inside edit, pass down v-bind="propBag", and that will use the correct props at the child level
for vue > 2.0
v-bind="$attrs" it's sufficient, or you can declare them at data(), with [this.$attrs]
Solution possible from Vue 2.2.0
provide / inject
This pair of options are used together to allow an ancestor component to serve as a dependency injector for all its descendants, regardless of how deep the component hierarchy is, as long as they are in the same parent chain.
https://fr.vuejs.org/v2/api/index.html#provide-inject
How to pass multiple Props Downstream to Components
Use case: Props that you only need on the n-th child components, you can simply forward downstream without having to define the same props all over again in each component.
Correction: v-bind="$attrs" works just fine. Just make sure the parent also uses v-bind="$attrs" and not v-bind="$attr" ('s' was missing) which was the error that made me think v-bind="{ ...$attrs }" was needed.
However, I think you should still be able to use v-bind="{ ...$attrs }" to access all previous attributes, even if parents didn't explicitly propagated them.
How to:
Based on Alexander Kim's comment, it must be v-bind="{ ...$attrs }".
... is needed to pass the attributes of the previous component (parent) as $attrs only passes the attributes of the current component.
v-bind="{ ...$attrs }"
You must pass all data via props to children components. You don't have to pass it as an object but Vue.js does require all data to be passed to children. From their documentation:
Every component instance has its own isolated scope. This means you cannot (and should not) directly reference parent data in a child component’s template. Data can be passed down to child components using props.
So you are doing it in the correct manner. You don't have to create an object, you are able to pass as many props as you would like but you do have to pass the from each parent to each child even if the parent is a child of the original "parent".