vue: passing props down to all descendants - vue.js

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".

Related

Understanding when to use : , # and without any in vue

I have been going through an already existing code and trying to understand different parameters passed inside a tag.
<some-element
placeholder ="show first name"
:someElement = "true"
#error = "showErrorAlert"
>
so what exactly is the difference between these three parameters passed and when to pass them.
I am very new to vue so I am struggling a bit.
If any one needs any further information please let me know.
to communicate data between components in vuejs we have two concept: props and events.
vue props
vue custom events
read the two links above to learn about them from vue documentation but in short say we have a component structure like this:
<parent-component>
---- | <child-component>
data flow from parent to child is done with the help of props. that is when you're defining child-component you can set in its vue instance object that this component can accept some prop like this.
props: {
text: { type: String },
value: { type: Boolean },
}
then when you use this component in the parent one you can pass the defined props to the component like this:
<child-component text="some text" :value="false" />
notice the : notation is just a shorthand for v-bind:value="false", if you don't use this binding the false will be sent to the child component as a string. for any other value type you should use binding.
please read the docs to learn more about binding in vue
attribute binding
v-bind shorthand
now, the data flow from child to parent is done via events so in your child component you can emit an event for the parent one to listen to like this:
this.$emit('event-name', payload)
then in the parent component where you used child component you can listen to this event like this
<child-component #event-name="doSomethingFun($event)" />
where doSomethingFun() is a method in parent component and we are passing the payload sent from child component to this method with $event
notice # notation is shorthand for v-on like v-on:event-name="doSomethingFun($event)"
: is shorthand for v-bind: and this is used for dynamic rendering for the attributes such as src, href etc.
# is shorthand for v-on:click which is event handler for function calls.
You can read more Event handling shorthand syntax

Confusion of Properties, Data, and Computed Values when Working with Components (especially nested)

I am really confused how to pass down properties from a parent and update them in children then emit those changes all the way back up to the parent. By reference it looks to work but from everything I read that is not the correct way to do it.
I have a parent component which "toggles" a create form. A new instance of the artifact is created and passed into a "create" component which is used in other places. The create component has some common properties but, through a slot, custom properties can be added at the parent level.
Within the ArtifactCreate component, it passes a "clone" of that prop to both the form and the custom properties.
I am trying to understand how to take the "prop" value, work with it internally, and then bubble up the final result to the ArtifactCreate component which in-turn bubbles that up to the parent.
The "child" components (ArtifactCreateForm and DatasetProperties) do not have any additional methods and are directly updating the prop reference passed in. DatasetProperties can be used in other places as well.
In most cases, I have a "parent" that I want to handle the main interaction but need to pass some model down to one or more components, which in turn may pass that in its children.
The structure looks close to this:
Parent Component (somewhat like the controller handling main actions)
|
|--- View Component (takes in 'artifact' prop and passes it down)
|
|--- Child Component
|
|--- GrandChild 1..x
|-----------------|--- GrandChild (through slot)
I am really confused on on the relationship between properties and data and/or computed values and how to correctly work with that data in the grandchildren. I am fine with the concept of pass data down and emit events up and I seem to grasp how to do that with primitive properties but how can I do this with an object that has many properties?
My questions are:
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
(FYI, coming from Java to Vue so this is a whole new world to me).
Parent Component
This is my entry into the create component. It manages the instance (in this case a new one) and passes it down to the ArtifactCreate component which internally clones it. The updated copy is bubbled back to here where it is saved.
<template>
<div>
<artifact-header/>
<v-container fluid>
<!-- List -->
<reference-list
:paths="paths"
#open="load"/>
<!-- Create Artifact -->
<artifact-create
:advanced="true"
:artifact="newArtifact"
:title="newTitle"
#save="save">
<template #default="{ model }">
<dataset-properties :artifact="model"/>
</template>
</artifact-create>
</v-container>
</div>
</template>
Supported by:
computed: {
newArtifact: {
get(): Dataset {
return newDataset();
}
}
},
methods: {
save(item: Dataset){
this.$store.dispatch("saveDataset", item);
},
ArtifactCreate Component (child / container component)
<app-dialog
:title="title"
:visible="visible"
#action="save"
#close="close">
<v-tabs v-if="advanced"
v-model="tab"
grow>
<v-tab>Basic</v-tab>
<v-tab>Advanced</v-tab>
<!-- Basic Properties -->
<v-tab-item :key="'Basic'">
<artifact-create-form :artifact="internal"/>
</v-tab-item>
<!-- Advanced Properties -->
<v-tab-item :key="'Advanced'">
<slot :model="internal"/>
</v-tab-item>
</v-tabs>
<!-- Basic Properties ONLY -->
<artifact-create-form v-else
:artifact="internal"/>
</app-dialog>
And it is supported with:
export default Vue.extend({
name: "ArtifactCreate",
props:{
artifact: {
type: Object as Prop<IArtifact>,
default: {}
},
},
computed:{
internal: {
get(): IArtifact {
return clone(this.artifact);
},
}
},
methods: {
save(item) {
this.$emit('save', this.internal);
this.visible = false;
},
},
});
ArtifactCreateForm Component (child in ArtifactCreate)
<template>
<v-form>
<v-text-field
v-model="artifact.name"
label="Name*"
required>
</v-text-field>
</v-form>
</template>
Supported by:
export default Vue.extend({
name: "ArtifactCreateForm",
props:{
artifact: {
type: Object as Prop<IArtifact>,
default: {}
},
},
});
DatasetProperties Component (child in ArtifactCreate / registered in parent through slot)
<template>
<v-form>
<v-text-field
v-model="artifact.source"
label="Source">
</v-text-field>
<v-text-field
v-model="artifact.primaryKey"
label="Primary Key">
</v-text-field>
</v-form>
</template>
Supported by:
export default Vue.extend({
name: 'DatasetProperties',
props:{
artifact: {
type: Object as Prop<Dataset>,
default: {}
}
},
})
I have Vuex in place and "could" use that but it seems like overkill for creating a new object? But similar to how to work with data still trying to get where/when it is appropriate.
Complex model and form handling is not straight forward and there are very few good examples how to handle complex forms with custom component v-model implementations especially if you want v-model on objects. I'll attempt to give my recommendations below.
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
It depends, if like in your example at the parent level you want to pass an initial object value through a prop and happy to wait until a final event occurs e.g. :artifact="newArtifact" #save="save", then that is the way to go. In other words, to me your Parent Component looks good.
However once you start working on child and lower components where you start working with the actual model properties via v-model it gets a bit more complex. See below.
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
As you pointed out in the original question, the "Vue way" way is -> data down and events up. In fact that is even how v-model work, remember v-model="model" is basically short for :value="model" #input="model = $event". But this gets a little messy when the model is an object and as mentioned above, there are few good examples on how to handle custom component v-model with complex objects as models.
See 3 below.
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
Using data updates by reference is not considered best practice for the reasons pointed out in #bviala answer. It also breaks the data down events up approach. Ideally, your child components ArtifactCreateForm and DatasetProperties should implement v-model functionality. As mentioned, implementing v-model in your custom component especially on objects is not as clean as you would hope. But you can implement helper functions to make it a bit easier.
For example, implementing v-model on your ArtifactCreateForm component, it would look like this:
<template>
<v-form>
<v-text-field
:value="value.name"
#input="update(m => (m.name = $event))
label="Name*"
required>
</v-text-field>
<v-text-field
:value="value.description"
#input="update(m => (m.description = $event))
label="Description"
required>
</v-text-field>
</v-form>
</template>
export default Vue.extend({
name: 'ArtifactCreateForm',
props:{
value: {
type: Object as Prop<IArtifact>
}
},
methods: {
update(cb: (m: IArtifact) => void) {
const model = clone(this.value);
cb(model);
this.$emit("input", model);
}
}
})
And you would use this as:
<app-dialog
:title="title"
:visible="visible"
#action="save"
#close="close">
<artifact-create-form v-model="internal"/>
</app-dialog>
For reference the following blog post helped me a lot on how to implement v-model with objects on custom components: https://simonkollross.de/posts/vuejs-using-v-model-with-objects-for-custom-components
What is the correct way to pass props and handle them inside the components (as a copy), then bubble that back up to the parent?
As a copy, like you did in ArtifactCreate : Clone the prop, mutate it however you want, send it to the parent with an event if needed.
But if your child component doesn't need to have its own internal value, you shouldn't clone the prop, but rather just emit events and let the parent handle the mutation. It would look like this in the child:
<v-text-field
:value="artifact.source"
label="Source"
#input="$emit('updateSource', $event)"
>
What's weird in your example is that your child component clones data that the parent just created. Couldn't the child be the one in charge of calling newDataset()?
When bubbling the event from lower components is the only way to repeat it until it reaches the parent?
Yes, the only proper way (see next answer)
I am using "objects" as the prop/data not individual values. The reference gets updated (which is a copy) then bubbled to the parent (as I want). Is this the right way?
What you did in ArtifactCreateForm DatasetProperties works but is considered an anti-pattern. The reason being that the data lives in the parent component and is mutated by the child, with the source of the mutation being unknown by the parent. It can lead to maintenance issues if your component hierarchy becomes complex. The correct way is to send events.
That's why in my opinion, you should split your components mindfully: Do you really have reusability potential for ArtifactCreateForm and DatasetProperties ? If not, you can make your life easier by sticking to a single ArtifactCreate component.

What is proper component composition and use of props in Vue.js?

I'm struggling with this concept in vue.js..
I'm assuming that a component in Vue is an entity with some (html) representation and internal data or state. The component can then change it's internal data based on user's interaction with the template and inform the 'outer world' about its internal changes via events.
But then to put the component in context of the application as a whole most components need to receive data from the 'outer world' which would be done via props. So for a component to be useful it most often needs to change not only it's internal state but also some data it was given from the outer context - but props cannot be mutated directly. The internal data is for the internal working of the component but the real purpose of a component is to transform the data in props.
Lets say we have a component which is, via props, given an object representing a user profile for instance. The role of the component is to let the user edit their profile.
- to avoid mutating the prop (or a subproperties of the prop), i'd add a local copy of the prop which the component could work with freely - but i'd also have to add a watch to update the local copy every time the prop gets updated by the parent via v-bind.
</template>
<input v-model="localUserProfile.name"/>
</template>
<script>
export default {
props: {
userProfile: {
type: Object,
required: true
}
},
data: function () {
return {
localUserProfile: this.userProfile
}
},
watch: {
userProfile (newVal) { this.localUserProfile = newVal }
}
}
</script>
I could replace the watch with a computed property based on the given prop and let the component work over the computed property but then where to assign the edited values? Use the computed property's setter and 'emit' on changes?
Both these cases seem like a lot of extra code for a very common and repetitive task. What are some other common approaches to this? Are any of my assumptions wrong?
You should not update the prop from the children component :
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.
You can read more about it here
If you have no other solution than updating if from the children component, it might be worth thinking about a different data flow strategy or design. (Components Basics)

Vue two way prop binding

Below is my current structure (which doesn't work).
Parent component:
<template>
<field-input ref="title" :field.sync="title" />
</template>
<script>
import Field from './input/Field'
export default {
components: {
'field-input': Field
},
data() {
return {
title: {
value: '',
warn: false
}
}
}
}
</script>
Child component:
<template>
<div>
<input type="text" v-model="field.value">
<p v-bind:class="{ 'is-invisible' : !field.warn }">Some text</p>
</div>
</template>
<script>
export default {
props: ['field']
}
</script>
The requirements are:
If parent's data title.warn value changes in parent, the child's class bind should be updated (field.warn).
If the child's <input> is updated (field.value), then the parent's title.value should be updated.
What's the cleanest working solution to achieve this?
Don't bind the child component's <input> to the parent's title.value (like <input type="text" v-model="field.value">). This is a known bad practice, capable of making your app's data flow much harder to understand.
The requirements are:
If parent's data title.warn value changes in parent, the child's class bind should be updated (field.warn).
This is simple, just create a warn prop and pass it from parent to child.
Parent (passing the prop to the child):
<field-input ref="title" :warn="title.warn" />
Child/template (using the prop -- reading, only):
<p v-bind:class="{ 'is-invisible' : !warn }">Some text</p>
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {warn: Boolean}
}
Notice that in the template it is !warn, not !title.warn. Also, you should declare warn as a Boolean prop because if you don't the parent may use a string (e.g. <field-input warn="false" />) which would yield unexpected results (!"false" is actually false, not true).
If the child's <input> is updated (field.value), then the parent's title.value should be updated.
You have a couple of possible options here (like using .sync in a prop), but I'd argue the cleanest solution in this case is to create a value prop and use v-model on the parent.
Parent (binding the prop using v-model):
<field-input ref="title" v-model="title.value" />
Child/template (using the prop as initial value and emitting input events when it changes):
<input type="text" :value="value" #input="$emit('input', $event.target.value)">
Child/JavaScript (declaring the prop and its expected type):
export default {
props: {value: String}
}
Click here for a working DEMO of those two solutions together.
There are several ways of doing it, and some are mentioned in other answers:
Use props on components
Use v-model attribute
Use the sync modifier (for Vue 2.0)
Use v-model arguments (for Vue 3.0)
Use Pinia
Here are some details to the methods that are available:
1.) Use props on components
Props should ideally only be used to pass data down into a component and events should pass data back up. This is the way the system was intended. (Use either v-model or sync modifier as "shorthands")
Props and events are easy to use and are the ideal way to solve most common problems.
Using props for two-way binding is not usually advised but possible, by passing an object or array you can change a property of that object and it will be observed in both child and parent without Vue printing a warning in the console.
Because of how Vue observes changes all properties need to be available on an object or they will not be reactive.
If any properties are added after Vue has finished making them observable 'set' will have to be used.
//Normal usage
Vue.set(aVariable, 'aNewProp', 42);
//This is how to use it in Nuxt
this.$set(this.historyEntry, 'date', new Date());
The object will be reactive for both component and the parent:
I you pass an object/array as a prop, it's two-way syncing automatically - change data in the
child, it is changed in the parent.
If you pass simple values (strings, numbers)
via props, you have to explicitly use the .sync modifier
As quoted from --> https://stackoverflow.com/a/35723888/1087372
2.) Use v-model attribute
The v-model attribute is syntactic sugar that enables easy two-way binding between parent and child. It does the same thing as the sync modifier does only it uses a specific prop and a specific event for the binding
This:
<input v-model="searchText">
is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
Where the prop must be value and the event must be input
3.) Use the sync modifier (for Vue 2.0)
The sync modifier is also syntactic sugar and does the same as v-model, just that the prop and event names are set by whatever is being used.
In the parent it can be used as follows:
<text-document v-bind:title.sync="doc.title"></text-document>
From the child an event can be emitted to notify the parent of any changes:
this.$emit('update:title', newTitle)
4.) Use v-model arguments (for Vue 3.0)
In Vue 3.x the sync modifier was removed.
Instead you can use v-model arguments which solve the same problem
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" #update:title="pageTitle = $event" />
5.) Use Pinia (or Vuex)
As of now Pinia is the official recommended state manager/data store
Pinia is a store library for Vue, it allows you to share a state across components/pages.
By using the Pinia store it is easier to see the flow of data mutations and they are explicitly defined. By using the vue developer tools it is easy to debug and rollback changes that were made.
This approach needs a bit more boilerplate, but if used throughout a project it becomes a much cleaner way to define how changes are made and from where.
Take a look at their getting started section
**In case of legacy projects** :
If your project already uses Vuex, you can keep on using it.
Vuex 3 and 4 will still be maintained. However, it's unlikely to add new functionalities to it. Vuex and Pinia can be installed in the same project. If you're migrating existing Vuex app to Pinia, it might be a suitable option. However, if you're planning to start a new project, we highly recommend using Pinia instead.

Changing a property of an object in child component using VueJs

I have a simple question but I can't find the answer to it.
This code (that belongs to a child component? is a good practice?
export default {
name: 'Wish',
props: ['wish'],
data () {
return {
isEditing: false
}
},
methods: {
completeWish () {
this.wish.done = true
},
...
Or I should update the done property of wish object in the parent using emit?
Thanks
As Amade mentioned is a bad practice.
You should use events in order to accomplish it properly from a design point of view.
<script>
export default {
name: 'Wish',
props: ['wish'],
methods: {
completeWish () {
this.$emit('wish-is-completed')
}
}
}
</script>
And then in parent you have v-on:wish-is-completed="handleCompletedWish" like:
// Parent template
<template>
<wish v-on:wish-is-completed="handleCompletedWish"></wish>
</template>
EDIT:
I understand the answer was downvoted because you actually are able to mutate properties of props (not a direct prop reference) and don't get a warn when you do that.
Does it mean you should do that?
No.
Props are created for one-directional data flow purpose. When you mutate props to notify a parent of something, it quickly leads to having hard to maintain state changes, because they are not implicit. Use events for child->parent communication as official documentation suggest in such cases.
All props form a one-way-down binding between the child property and
the parent one: when the parent property updates, it will flow down to
the child, but not the other way around. This prevents child
components from accidentally mutating the parent’s state, which can
make your app’s data flow harder to understand.
Vue docs advise against mutating the props in the child component:
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.
So based on this I would tend to say it's a bad practice.
documentation:
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive
if you want add or change property use Vue.set like this:
this.$set(this.wish, 'done', true)
see example App:
https://3xyx386q65.codesandbox.io/setProps
see example code (open components/setProps/childComponent.vue):
https://codesandbox.io/s/3xyx386q65
note! wish init as Object! See components/setProps/setProps.vue in example code