How can I implicitly pass data to all child components in Vue? - javascript

Is there any way to implicitly pass data to all the child components of a parent? I'm looking for something similar to context in React. Here's a quick example of what I'm trying to accomplish:
<body>
<my-component :my-prop="foo"></my-component>
<my-component :my-prop="bar"></my-component>
</body>
-
import MyComponent from './my-component';
new Vue({
el: 'body',
components: {
MyComponent,
},
});
I'd like all of my-component's children to have access to myProp without having to pass it down every time. $root sounded like a good idea, but then I'd have to new up a vm for every component on my page, which doesn't feel to nice either.

From child component you can get parent component property like
this.$parent.myProp
https://vuejs.org/guide/components.html#Parent-Chain
Edit: Create function to find property in parents:
getProperty: function(vueComp, propName) {
var p = vueComp.$parent;
while (p) {
if(p.hasOwnProperty(propName)){
return p[propName];
}
p = p.$parent;
}
return null;
}
Use it like: var x = getProperty("myProp");

Related

How to create Vue.js slot programmatically?

I have the following component with a slot:
<template>
<div>
<h2>{{ someProp }}</h2>
<slot></slot>
</div>
</template>
For some reasons, I have to manually instantiate this component. This is how I am doing it:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
}).$mount(body);
The problem is: I am not able to create slot contents programmatically. So far, I can create simple string based slot:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
instance.$slots.default = ['Hello'];
instance.$mount(body);
The question is - how can I create $slots programmatically and pass it to the instance I am creating using new?
Note: I am not using a full build of Vue.js (runtime only). So I don't have a Vue.js compiler available to compile the template on the fly.
I looked into TypeScript definition files of Vue.js and I found an undocumented function on Vue component instance: $createElement(). My guess is, it is the same function that is passed to render(createElement) function of the component. So, I am able to solve it as:
const Constr = Vue.extend(MyComponent);
const instance = new Constr({
propsData: { someProp: 'My Heading' }
});
// Creating simple slot
const node = instance.$createElement('div', ['Hello']);
instance.$slots.default = [node];
instance.$mount(body);
But this is clearly undocumented and hence questionable approach. I will not mark it answered if there is some better approach available.
I think I have finally stumbled on a way to programmatically create a slot element. From what I can tell, the approach does not seem to work for functional components. I am not sure why.
If you are implementing your own render method for a component, you can programmatically create slots that you pass to child elements using the createElement method (or whatever you have aliased it to in the render method), and passing a data hash that includes { slot: NAME_OF_YOUR_SLOT } followed by the array of children within that slot.
For example:
Vue.config.productionTip = false
Vue.config.devtools = false;
Vue.component('parent', {
render (createElement) {
return createElement('child', [
createElement('h1', { slot: 'parent-slot' }, 'Parent-provided Named Slot'),
createElement('h2', { slot: 'default' }, 'Parent-provided Default Slot')
])
}
})
Vue.component('child', {
template: '<div><slot name="parent-slot" /><slot /></div>'
})
new Vue({
el: '#app',
template: '<parent />'
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id='app'>
</div>
(This doesn't really answer How to create Vue.js slot programatically?. But it does solve your problem.)
This trick is less hackish compared to using $createElement().
Basically, create a new component that register MyComponent as a local component.
const Constr = Vue.extend({
template: `
<MyComponent someProp="My Heading">
<div>slot here !!!</div>
</MyComponent>
`,
components: {
MyComponent: MyComponent
}
});
const instance = new Constr().$mount('#app');
Demo: https://jsfiddle.net/jacobgoh101/shrn26p1/
I just came across an answer to this in vue forum:
slots
The principle is: There is nothing like createElement('slot'..)
Instead there is a render function which provides the slotted innerHtml as function:
$scopedSlots.default()
Usage:
render: function (createElement) {
const self = this;
return createElement("div", this.$scopedSlots.default());
}
If you want to provide a default in case there is no content given for the slots, you need to code a disctinction yourself and render something else.
(The link above holds a more detailed example)
The function returns an array, therefore it can not be used as a root for the render function. It need to be wrapped into single container node like div in the example above.

Call parent method with component

I have a component and want to add a click listener that runs a method in the parent template in Vue. Is this possible?
<template>
<custom-element #click="someMethod"></custom-element>
</template>
<script>
export default {
name: 'template',
methods: {
someMethod: function() {
console.log(true);
}
}
</script>
Yes!
It's possible to call a parent method from a child and it's very easy.
Each Vue component define the property $parent. From this property you can then call any method that exist in the parent.
Here is a JSFiddle that does it : https://jsfiddle.net/50qt9ce3/1/
<script src="https://unpkg.com/vue"></script>
<template id="child-template">
<span #click="someMethod">Click me!</span>
</template>
<div id="app">
<child></child>
</div>
<script>
Vue.component('child', {
template: '#child-template',
methods: {
someMethod(){
this.$parent.someMethod();
}
}
});
var app = new Vue({
el: '#app',
methods: {
someMethod(){
alert('parent');
}
}
});
</script>
Note: While it's not recommended to do this kind of thing when you are building disconnected reusable components, sometimes we are building related non-reusable component and in this case it's very handy.
Directly from the Vue.js documentation:
In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events...
So you need to emit a click event from your child component when something happens, which can then be used to call a method in your parent template.
If you don't want to explicitly emit an event from the child (using this.$emit('click') from your child component), you can also try to use a native click event, #click.native="someMethod".
Relying on calling this.$parent hides the dependency and will break when you use component libraries which create a longer child hierarchy
The prefered methods are to either:
Explicitly pass methods as properties to child components (the same as passing data props)
Or declare global methods as mixins
From nils's answer on Vue.js inheritance call parent method:
Passing props (parent-child)
var SomeComponentA = Vue.extend({
methods: {
someFunction: function () {
// ClassA some stuff
}
}
});
var SomeComponentB = Vue.extend({
props: [ 'someFunctionParent' ],
methods: {
someFunction: function () {
// Do your stuff
this.someFunctionParent();
}
}
});
and in the template of SomeComponentA:
<some-component-b :someFunctionParent="someFunction"></some-component-b>
Use Mixins
If this is common functionality that you want to use in other places, using a mixin might be more idiomatic:
var mixin = {
methods: {
someFunction: function() {
// ...
}
}
};
var SomeComponentA = Vue.extend({
mixins: [ mixin ],
methods: {
}
});
var SomeComponentB = Vue.extend({
methods: {
someFunctionExtended: function () {
// Do your stuff
this.someFunction();
}
}
});
Further Reading
Vue.js - Making helper functions globally available to single-file components
Vue Docs Mixins
You can either pass the parent method down to the child component via props or you can get the child component to emit either a custom or native event.
Here's a Plunker to demonstrate both approaches.
You can use $root like this with vanilla Vue, but, If you use nuxt with vue that response won't work. Why? because $root is nuxt itself. Let me show you an example:
this.$root.$children[1].myRootMethod()
$root: As I said before, this is nuxt.
$children[0]: this is nuxtloading.
$children[1]: this is your main component, in my case, it was a basic layout with a few global components and a global mixin.
$children[n]: other components on your app.
Hope it helps.
In current vue version, this solution:
Passing props (parent-child)
var SomeComponentA = Vue.extend({
methods: {
someFunction: function () {
// ClassA some stuff
}
}
});
var SomeComponentB = Vue.extend({
props: [ 'someFunctionParent' ],
methods: {
someFunction: function () {
// Do your stuff
this.someFunctionParent();
}
}
});
The HTML part:
<some-component-b someFunctionParent="someFunction"></some-component-b>
Base on this post, should be modified in this way:
<some-component-b v-bind:someFunctionParent="someFunction"></some-component-b>
Solution for Vue 3:
Another way to provide a function to a child component is to use provide/inject. The advantage is that you can avoid passing on props through multiple components, as inject can be used at any depth of child components.
Parent component:
<script setup>
import { provide } from 'vue'
myFunction(){
console.log('message from parent');
}
provide('message', myFunction);
Some child component:
<script setup>
import { inject } from 'vue'
const msg = inject('message')
//calling the injected function
msg()
</script>
Link to the docs.

Vue how to call filter from parent

How can I be able to call filter of parent using single file component. Below are my code.
app.js
import computed from '../vue/mixins/computed.js';
import filters from '../vue/mixins/filters.js';
import methods from '../vue/mixins/methods.js';
const app = new Vue({
el: '#app',
mixins:[
computed,
filters,
methods
],
mounted: function() {
}
});
home.vue
<template>
<div class="home-content">
<h3>{{home | uppercase}}</h3>
</div>
</template>
<script type="text/javascript">
export default {
data: function() {
return {
home: 'home'
}
},
mounted: function() {
this.$parent.$options.methods.makeConsole();
}
}
</script>
It's giving me this warning in console "Failed to resolve filter: uppercase"
You should just make the filter global available before starting the root instance with
Vue.filter('uppercase', uppercase);
Where uppercase can be a simple function like
function uppercase(str)
return str.uppercase();
}
That would be the most simple and reliable way to use the filter on all vue components;
If you import your filters to your parent via mixins why don't you use that mixin in the child?
Please do not use the this.$parent-method as it makes your child component statical dependend of that parent.
To use the $parent approach you may need to declare the filter function from the parent as a filter in the child:
filters:{
uppercase: this.$parent.$options.filters.uppercase
}
There is no point. Just include your mixin in the child as well. A component should ideally be autonomous, and not aware of where it is in the hierarchy of components (at least not the ones above or on the same level.

VueJS pass props to children in render function call

I have a parent component that wraps multiple children 'child'. I want the parent to essentially have the same template as the vanilla markup which is why I am using the render function.
I want the parent to manage the state of which child is active. But the props don't appear to be passing down to the children.
I have tried applying what the documentation says, but props are undefined on the children. However if I do item.data.staticClass = 'testing-class'; that class applies to each child.
Vue.component('parent', {
data: function() {
return {
activeIndex: 0
}
},
render: function(createElement) {
this.$options._renderChildren.forEach(function(item, index) {
if (item.data === undefined) //whitespace?
return;
item.data.props = {
activeindex: this.activeIndex,
index: index
}
}.bind(this));
return createElement('div', {}, this.$options._renderChildren);
}
});
Vue.component('child', {
template: '<div>Im a child</div>',
props: ['activeindex', 'index'],
mounted: function() {
console.log(this.$props); //undefined
}
});
new Vue({
el: '#app'
});
JSFIDDLE DEMO
First of all, I think that this.$props will always be undefined. The $props property is accessible in a template like {{ $props }}, but those inline properties (frustratingly) don't always map up directly to the this variable available in the component's script. You can see the component's prop values using this.$options.propsData.
Secondly, you can use item.componentOptions.propsData to set the property values of the child component. (I think item.data.props is a misnomer referencing something else). Here's a fiddle with the change.

Call a Vue.js component method from outside the component

Let's say I have a main Vue instance that has child components. Is there a way of calling a method belonging to one of these components from outside the Vue instance entirely?
Here is an example:
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
$('#external-button').click(function()
{
vm['my-component'].increaseCount(); // This doesn't work
});
<script src="http://vuejs.org/js/vue.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="app">
<my-component></my-component>
<br>
<button id="external-button">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 5px;">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
So when I click the internal button, the increaseCount() method is bound to its click event so it gets called. There is no way to bind the event to the external button, whose click event I am listening for with jQuery, so I'll need some other way to call increaseCount.
EDIT
It seems this works:
vm.$children[0].increaseCount();
However, this is not a good solution because I am referencing the component by its index in the children array, and with many components this is unlikely to stay constant and the code is less readable.
In the end I opted for using Vue's ref directive. This allows a component to be referenced from the parent for direct access.
E.g.
Have a component registered on my parent instance:
var vm = new Vue({
el: '#app',
components: { 'my-component': myComponent }
});
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Now, elsewhere I can access the component externally
<script>
vm.$refs.foo.doSomething(); //assuming my component has a doSomething() method
</script>
See this fiddle for an example: https://jsfiddle.net/0zefx8o6/
(old example using Vue 1: https://jsfiddle.net/6v7y6msr/)
Edit for Vue3 - Composition API
The child-component has to return the function in setup you want to use in the parent-component otherwise the function is not available to the parent.
Note: <sript setup> doc is not affacted, because it provides all the functions and variables to the template by default.
You can set ref for child components then in parent can call via $refs:
Add ref to child component:
<my-component ref="childref"></my-component>
Add click event to parent:
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
var vm = new Vue({
el: '#app',
components: {
'my-component': {
template: '#my-template',
data: function() {
return {
count: 1,
};
},
methods: {
increaseCount: function() {
this.count++;
}
}
},
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-component ref="childref"></my-component>
<button id="external-button" #click="$refs.childref.increaseCount()">External Button</button>
</div>
<template id="my-template">
<div style="border: 1px solid; padding: 2px;" ref="childref">
<p>A counter: {{ count }}</p>
<button #click="increaseCount">Internal Button</button>
</div>
</template>
For Vue2 this applies:
var bus = new Vue()
// in component A's method
bus.$emit('id-selected', 1)
// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})
See here for the Vue docs.
And here is more detail on how to set up this event bus exactly.
If you'd like more info on when to use properties, events and/ or centralized state management see this article.
See below comment of Thomas regarding Vue 3.
You can use Vue event system
vm.$broadcast('event-name', args)
and
vm.$on('event-name', function())
Here is the fiddle:
http://jsfiddle.net/hfalucas/wc1gg5v4/59/
A slightly different (simpler) version of the accepted answer:
Have a component registered on the parent instance:
export default {
components: { 'my-component': myComponent }
}
Render the component in template/html with a reference:
<my-component ref="foo"></my-component>
Access the component method:
<script>
this.$refs.foo.doSomething();
</script>
Say you have a child_method() in the child component:
export default {
methods: {
child_method () {
console.log('I got clicked')
}
}
}
Now you want to execute the child_method from parent component:
<template>
<div>
<button #click="exec">Execute child component</button>
<child-cmp ref="child"></child_cmp> <!-- note the ref="child" here -->
</div>
</template>
export default {
methods: {
exec () { //accessing the child component instance through $refs
this.$refs.child.child_method() //execute the method belongs to the child component
}
}
}
If you want to execute a parent component method from child component:
this.$parent.name_of_method()
NOTE: It is not recommended to access the child and parent component like this.
Instead as best practice use Props & Events for parent-child communication.
If you want communication between components surely use vuex or event bus
Please read this very helpful article
This is a simple way to access a component's methods from other component
// This is external shared (reusable) component, so you can call its methods from other components
export default {
name: 'SharedBase',
methods: {
fetchLocalData: function(module, page){
// .....fetches some data
return { jsonData }
}
}
}
// This is your component where you can call SharedBased component's method(s)
import SharedBase from '[your path to component]';
var sections = [];
export default {
name: 'History',
created: function(){
this.sections = SharedBase.methods['fetchLocalData']('intro', 'history');
}
}
Using Vue 3:
const app = createApp({})
// register an options object
app.component('my-component', {
/* ... */
})
....
// retrieve a registered component
const MyComponent = app.component('my-component')
MyComponent.methods.greet();
https://v3.vuejs.org/api/application-api.html#component
Here is a simple one
this.$children[indexOfComponent].childsMethodName();
I am not sure is it the right way but this one works for me.
First import the component which contains the method you want to call in your component
import myComponent from './MyComponent'
and then call any method of MyCompenent
myComponent.methods.doSomething()
Declare your function in a component like this:
export default {
mounted () {
this.$root.$on('component1', () => {
// do your logic here :D
});
}
};
and call it from any page like this:
this.$root.$emit("component1");
If you're using Vue 3 with <script setup> sugar, note that internal bindings of a component are closed (not visible from outside the component) and you must use defineExpose(see docs) to make them visible from outside. Something like this:
<script setup lang="ts">
const method1 = () => { ... };
const method2 = () => { ... };
defineExpose({
method1,
method2,
});
</script>
Since
Components using are closed by default
Sometimes you want to keep these things contained within your component. Depending on DOM state (the elements you're listening on must exist in DOM when your Vue component is instantiated), you can listen to events on elements outside of your component from within your Vue component. Let's say there is an element outside of your component, and when the user clicks it, you want your component to respond.
In html you have:
Launch the component
...
<my-component></my-component>
In your Vue component:
methods() {
doSomething() {
// do something
}
},
created() {
document.getElementById('outsideLink').addEventListener('click', evt =>
{
this.doSomething();
});
}
I have used a very simple solution. I have included a HTML element, that calls the method, in my Vue Component that I select, using Vanilla JS, and I trigger click!
In the Vue Component, I have included something like the following:
<span data-id="btnReload" #click="fetchTaskList()"><i class="fa fa-refresh"></i></span>
That I use using Vanilla JS:
const btnReload = document.querySelector('[data-id="btnReload"]');
btnReload.click();

Categories