Passing a prop down 2 layers of children? - vue.js

I have a component and I pass in an id as a prop:
<comments myId="1"></comments>
And on the comments component I have it as a prop:
props: [
'myId',
],
Inside this comments component template I have another component
<btn id="{{ this.myId }}"></btn>
But i cannot seem to pass the prop down - I get the error:
Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id="{{ val }}">, use <div :id="val">.
I don't see why I need to use :, I'm happy to pass the id as a string.
How can I resolve the error, and pass down the prop?

you can write down
<btn :id="myId"></btn>
to pass the props in to component.
syntax for passing props is this we can bind variable to component using bind we don't need to interpolate values there.
Vue.component('child', {
template: '#child',
props: ['id']
});
Vue.component('childchild', {
template: '#childchild',
props: ['id']
});
new Vue({
el: '#app',
data: {
},
created: function() {
},
methods: {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
<child id="1000"></child>
</div>
<template id="child">
<childchild :id="id"></childchild>
</template>
<template id="childchild">
<h1>{{ id }}</h1>
</template>

Related

Why do I need the v-bind when I have the v-on?

In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.

Vue mutate prop binded by v-bind with sync modifier

I have an object in my component data. Now, I'm just binding all the properties of the object as a prop to the child component using v-bind.sync directive. I'm updating these props from the child component using the built-in update event but still, I'm getting Avoid mutation props directly error in the console. Here is the minimal example attached.
Parent Component
<template>
<div>
<oslo v-bind.sync="data" />
</div>
</template>
<script>
import Oslo from '#/components/Oslo.vue'
export default {
components: {
Oslo,
},
name: 'OsloParent',
data() {
return {
data: {
data: {
name: 'Oslo name',
access: 'admin'
}
},
}
},
}
</script>
Child component
<template>
<div>
<input type="text" v-model="name" #keyup="$emit('update:name', name)" />
<input type="text" v-model="access" #keyup="$emit('update:access', access)" />
</div>
</template>
<script>
export default {
props: {
name: String,
access: String
},
name: 'Oslo',
}
</script>
This is just an example component I've created for the reproduction of the problem. The actual component is supposed to handle so many props with two-way binding and that's the reason I'm binding the data with v-bind directive with sync modifier. Here is the Vue warning from the console (most common).
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "name"
Any suggestions to improve this or silent the Vue warn for this specific case? The above-given components works as desired, Vue throws error though.
I found two problems with your example that might throw this off.
The use of v-model directly to the property. Use v-bind instead to have it only display. And use v-on:change handler to fire the $emit('update:propertyname', value) and send the new value to update on the object.
The value sent along in the $emit seems empty and thus makes no change. Use $event.target.value instead.
Side note: v-on:keyup might not be the best event to listen to, since input can also be drag-and-dropped. Listening to v-on:change would be beter in that case.
Note on event listeners when using only v-bind.sync instead of v-bind:propertyName.sync:
If you want to listen to the update:propertyName event from the child component on the parent, you have to use the .capture modifier. Otherwise the update event is caught by the v-on:update:propertyName on the child component and this does not bubble up to the parent.
So you can use v-on:update:name.capture="someMethod" on the <oslo> tag for example. And have this someMethod in the parent's methods. After this is called, the event will be triggered on the child component which will update the object and thereby the property.
All together:
let Oslo = {
props: {
name: String,
access: String
},
name: 'Oslo',
template: `<div>
<input type="text" :value="name" #change="$emit('update:name', $event.target.value)" />
<input type="text" :value="access" #change="$emit('update:access', $event.target.value)" />
</div>`
}
new Vue({
el: "#app",
components: {
Oslo,
},
data: {
thedata: {
name: 'Oslo name',
access: 'admin'
}
},
methods: {
nameWillBeUpdated: function(v) {
console.log('New value of name will be:', v);
// After this, the `update:name` event handler of the
// child component is triggered and the value will change.
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<span>{{this.thedata.name}} - {{this.thedata.access}}</span>
<oslo
v-bind.sync="thedata"
v-on:update:name.capture="nameWillBeUpdated"
/>
</div>
You can just pass an object and sync it instead of individual properties if you have many properties to listen to from child component. See the example below:
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.component('Oslo', {
template: `
<div>
<input type="text" v-model="comp_name" #keyup="$emit('update:name', comp_name)" />
<input type="text" v-model="comp_access" #keyup="$emit('update:access', comp_access)" />
</div>
`,
props: {
data: {
name: String,
access: String,
}
},
data() {
return {
comp_name: this.data.name,
comp_access: this.data.access
}
}
})
new Vue({
el: '#app',
data() {
return {
doc: {
name: 'Oslo name',
access: 'admin'
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<span>---- {{ this.doc.name }}----</span>
<span>---- {{ this.doc.access }}----</span>
<oslo :data="this.doc" v-bind.sync="doc" />
</div>
</div>

How to make a template variable non-reactive in Vue

I have an edit form with variables held in the data(). I don't want the title of the edit page to update yet I want to maintain the v-model sync of data between the input and data. What's the simplest way to make the title non-reactive in the h1 tag? Mr You has to have something up his sleeve for this..
<template>
<div>
<h1>{{ title }}</h1>
<input v-model="title">
</div>
</template>
<script>
export default {
data: {
title: 'Initial value'
}
}
</script>
The Vue docs recommend Object.freeze() on the returned object in data() to disable reactivity on properties:
data() {
return Object.freeze({ title: 'Initial value' })
}
But the caveat is it freezes all properties (it doesn't look like there's a way to freeze only some properties using this method), and using v-model with this causes console errors (Cannot assign to read only property).
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return Object.freeze({
message: 'Hello Vue.js!',
})
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<p>{{ message }}</p>
<input v-model="message"> <!-- XXX: Cannot use v-model with frozen property. This will cause a console error. -->
</div>
Alternatively, you could arbitrarily remove the reactivity from any configurable data property by redefining it with writeable: false:
methods: {
removeReactivity() {
Object.defineProperty(this, 'title', {value: null, writeable: false});
}
}
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data() {
return {
message: 'Hello Vue.js!',
}
},
methods: {
removeReactivity() {
Object.defineProperty(this, 'message', {value: null, writeable: false});
}
}
})
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<p>{{ message }}</p>
<input v-model="message">
<div>
<button #click="removeReactivity">
Remove reactivity for <code>message</code>
</button>
</div>
</div>
You could potentially use v-once directive for your purpose if you don't want to create a separate variable for input. From the docs:
Render the element and component once only. On subsequent re-renders,
the element/component and all its children will be treated as static
content and skipped.
new Vue({
el: "#app",
data: {
title: "initial value"
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.min.js"></script>
<div id="app">
<input v-model="title">
<p>Reactive title: {{ title }}</p>
<p v-once>Static title: {{ title }}</p>
</div>
If you don't want the input to change the value of your data item, use value to bind it rather than the two-way v-model. Then it just acts as an initializer for the input.
If you want to have two values, one that doesn't change and one that does that gets initialized from the other, you need to have two data items. The non-changing one can be a prop with a default value. The other is a data member which, if you use a data function, can initialize itself to the prop value.
new Vue({
el: '#app',
props: {
initTitle: {
default: 'Initial value'
}
},
data() {
return {
title: this.initTitle
};
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<h1>{{ initTitle }}</h1>
<input v-model="title">
<div>Title is "{{title}}"</div>
</div>
You could alternatively use the little-known $options properties to define your title as a sort of internal constant rather than a prop. I am of mixed feelings about whether this is a good design approach or a step too weird.
new Vue({
el: '#app',
initTitle: 'Initial value',
data() {
return {
title: this.$options.initTitle
};
}
});
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<h1>{{ $options.initTitle }}</h1>
<input v-model="title">
<div>Title is "{{title}}"</div>
</div>
Working backwards from the contents of this blog...
It appears that when you create an object for Vue, it creates the properties with reactive getters and setters. If you then append a property to that object out-of-band, then it won't get the reactive capability, but will still be accessible as a value.
This should solve it for you:
<template>
<div>
<h1>{{ titleContainer.value }}</h1>
<input v-model="title">
</div>
</template>
<script>
export default {
data: {
titleContainer: {}
}
}
titleContainer.value = "Initial Value"
</script>
There is no easy way to solve your problem with Vue as is since Vue automatically injects reactive getters and setters for all object properties. You could use Object.freeze() on the variable to remove reactivity BUT it would apply across the whole object itself which is not what you want.
I created a fork out of vue called vue-for-babylonians to restrict reactivity and even permit some object properties to be reactive. Check it out here.
With it, you can tell Vue to not make any objects which are stored in vue or vuex from being reactive. You can also tell Vue to make certain subset of object properties reactive. You’ll find performance improves substantially and you enjoy the convenience of storing and passing large objects as you would normally in vue/vuex.

Vue.js: passing an object as a prop, then using its properties as attributes in a child component

I'm trying to do this. It seems trivial, but is not working.
In my parent component, I'm instantiating the child EndpointDetailsForm component, and passing it the settingsDetails prop, like this:
<EndpointDetailsForm :endpointDetails="modalDetails.content" />
Inside the EndpointDetailsForm component, I'm retrieving the endpointDetails object, like this:
props: {
endpointDetails: {
type: Object
}
}
and trying to use its various properties as attributes, like this:
<b-form-input id="nameInput"
type="text"
v-model="form.name"
:placeholder="endpointDetails.name">
</b-form-input>
When I inspect the EndpointDetailsForm component, it shows me the endpointDetails as a prop, but when I inspect the input above, it tells me that the placeholder is null.
What am I missing?
In your template you have to use kebab-cased attributes. Vue will convert them to camelCased props:
HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents.
Therefore if your prop is named endpointDetails, you should refer to it as an attribute as endpoint-details. Therefore:
<EndpointDetailsForm :endpoint-details="modalDetails.content" />
Code example:
Vue.component('b-form-input', {
template: '#b-form-input',
props: {
placeholder: String,
},
});
Vue.component('endpointetailsform', {
template: '#EndpointDetailsForm',
props: {
// Vue converts kebab-case to camelCase.
endpointDetails: {
type: Object
},
},
});
new Vue({
el: '#app',
data: {
content: {
name: 'my placeholder',
},
},
});
<script src="https://unpkg.com/vue#2"></script>
<div id="app">
<!-- Use kebab-cased attributes -->
<endpointetailsform :endpoint-details="content" />
</div>
<template id="EndpointDetailsForm">
<b-form-input :placeholder="endpointDetails.name"></b-form-input>
</template>
<template id="b-form-input">
<input :placeholder="placeholder" />
</template>

How to pass multiple props from parent to child component in Vue

I'm trying to pass two properties from parent to child, but for some reason this isn't working and all the examples I've found refer to passing a single property. What I've tried to do is:
Parent vue component:
<template>
<div class="statistics_display">
<multiLineChart :rowsA="reading['A'].price_stats" :rowsB="reading['B'].price_stats"></multiLineChart>
</div>
</template>
multiLineChart vue component:
export default {
name: 'MultiLineChart',
props: ['rowsA', 'rowsB'],
mounted: function() {
console.log(this.rowsA);
}
the console log is returning undefined. If I executethe exact same code and pass a single prop, it returns the expected prop contents. What am I missing?
HTML attributes are case-insensitive, so
<multiLineChart :rowsA="reading['A'].price_stats" :rowsB="reading['B'].price_stats"></multiLineChart>
Are actually bound to props: ['rowsa', 'rowsb'].
If you want props: ['rowsA', 'rowsB']to work, use, in the template: :rows-a="..." and :rows-b="...".
See it working below.
Vue.component('multilinechart', {
template: "#mtemplate",
props: ['rowsA', 'rowsB'],
mounted: function() {
console.log(this.rowsA, this.rowsB);
}
})
new Vue({
el: '#app',
data: {
reading: {A: {price_stats: 11}, B: {price_stats: 22}}
}
});
<script src="https://unpkg.com/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<div class="statistics_display">
<multiLineChart :rows-a="reading['A'].price_stats" :rows-b="reading['B'].price_stats"></multiLineChart>
</div>
</div>
<template id="mtemplate">
<div>I'm multilinechart</div>
</template>