Vue - Nesting components without separating into single page - vue.js

I am new in Vue and try to learn how the component structure works in Vue.
below is my code in codepen.
Nesting Components
<div id="todolist">
<first-layer>
<second-layer></second-layer>
</first-layer>
</div>
<script>
var secondLayer = Vue.extend({
template: '<div>i am second layer.</div>'
});
var firstLayer = Vue.extend({
template: '<div>I am first layer.</div>',
components: secondLayer,
});
var todolist = new Vue({
el: "#todolist",
components: {
'first-layer': firstLayer,
}
});
<script>
What I try to do is to separate component out of its parent by declare an object then call it in components property.
It worked at first layer.
But, when I try to do same thing in a component ( like nesting them) with same moves, the second layer didn't show up as expected. Why is that?
And what is the recommended structure to handle these without .vue file sep

This is happening because Vue overrides any template you put inside <first-layer> with the component's template.
To prevent it, you can use slots:
var firstLayer = Vue.extend({
template: '<div>I am first layer1.<slot></slot></div>'
});
now every content you put between <first-layer> tag will go into this slot.
If you want a more complicated component with different slots, you can use named slots

Use second-layer inside first-layer template as
template: '<div>I am first layer1.<div><second-layer></second-layer></div></div>',
Working example
var secondLayer = Vue.extend({
template: '<div>i am second layer2.</div>'
});
var firstLayer = Vue.extend({
template: '<div>I am first layer1.<div><second-layer></second-layer></div></div>',
components: {
'second-layer': secondLayer,
}
});
var todolist = new Vue({
el: "#todolist",
components: {
'first-layer': firstLayer,
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="todolist">
<first-layer>
</first-layer>
</div>
using <first-layer> tag means it will be replacef by template what ever you put inside this will be lost, to paas some param we can bind like <first-layer :name='name'> thats why your example is not working with nested tag

Related

How to display stub component when component is not found in vue

I am trying to catch situation, when component is not found, ie:
{
template: '<some-unknown-component></some-unknown-component>'
}
At that moment, Vue warns us with unknown custom element: <some-unknown-component>...
I would like to step in when some-unknown-component is not found and then use another component instead, like stub-component:
{
name: 'stub-component',
props: ['componentName'],
template: '<p>component ${componentName} does not exists, click here to create...</p>'
}
UPDATE: I am looking for solution without changing the template itself, so no v-if and component added.
Vue exposes a global error and warning handler. I managed to get a working solution by using the global warnHandler. I don't know if it is exactly what you are looking for, but it may be a good starting point. See the working snippet (I think it is quite self explanatory).
Vue.config.warnHandler = function (err, vm, info) {
if (err.includes("Unknown custom element:")) {
let componentName = err.match(/<.*>/g)[0].slice(1, -1)
vm.$options.components[componentName] = Vue.component('stub-component', {
props: ['componentName'],
template: `<p>component "${componentName}" does not exists, click here to create...</p>`,
});
vm.$forceUpdate()
} else {
console.warn(err)
}
};
new Vue({
el: '#app',
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<unknown-component></unknown-component>
</div>
Vue stores the details of all the registered components in the $options.component property of the Vue instance.
So, you can check for the component availability using this.$options.component and if the component is present then load the component otherwise load the other component.
In the below example, suppose you have two different components and you want to load them on the availability, then you can create a computed property on the basis of it, load the component as needed.
var CustomComponent = Vue.extend({ template: '<h2>A custom Component</h2>' });
var AnotherComponent = Vue.extend({ template: '<h2>Custom component does not exist.</h2>' });
new Vue({
el: "#app",
components: {
CustomComponent,
AnotherComponent
},
computed: {
componentAvailable () {
return this.$options.components.CustomComponent
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-if="componentAvailable">
<custom-component />
</div>
<div v-else>
<another-component />
</div>
</div>

Show value in template in VueJS

I have some code below, I want show "header" in template and show or hide when header not null.
<div id="hung">
<cmx-test v-bind:header="${properties.header}"></cmx-test>
</div>
<script>
Vue.component('cmx-test', {
props: ['header'],
template: '<h1 class=\"fillColor\" data={{this.header}}></h1>'
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#hung'
});
</script>
There is some syntax mistake in your code, to insert text into tag you can achieve with v-text attribute not data={{this.header}}.
And if you wanna hide some tag or component based on data value, you can use v-if.
And the last thing is if you wanna pass value intro component you can achieve that with this way v-bind:header="value", and value is variable which hold value you wanna pass.
<div id="hung">
<cmx-test v-if="header" v-bind:header="value"></cmx-test>
<button #click="header = true">Display</button>
</div>
<script>
Vue.component('cmx-test', {
props: ['header'],
template: '<h1 class=\"fillColor\" v-text="header"></h1>'
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#hung',
data: {
header: null,
value: "Hello World!"
}
});
</script>
Your question is not totally clear. You have the component receiving the prop header but header is not defined on the main Vue instance. You need to be passing that prop from the main data object / Vue instance into the component to use it there.
<div id="hung">
<cmx-test v-if="header" :header="header"></cmx-test>
</div>
<script>
Vue.component('cmx-test', {
props: ['header'],
template: '<h1 class=\"fillColor\" :data="header">{{header}}</h1>'
});
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#hung'
data:{
header: null,
}
});
</script>
Also using the same data object header to control whether the component is rendered or not using v-if. So if header is null or false it wont be rendered. When it becomes true or contains a value it will be rendered and the value of header will be passed to component through binding it (e.g. :header="header")

Vuejs vue-nav-tabs change title of tabs [duplicate]

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();

How can I compile the inner component as a child in Vue.js?

I know that normally all components are compiled globally and as siblings to each other. But I'm wondering how can I use the property of parent component? For example:
Vue.component('parent',{
template: '#parent',
props:['tab']
});
Vue.component('child',{
template: '#child',
props:['scope']
});
new Vue({
el: 'body',
data: function(){
return {
tab: "global"
}
}
});
<parent tab="parent">
<child slot="child" :scope="tab"></child>
</parent>
<template id="parent">
<h1>Parent</h1>
<slot name="child"></slot>
</template>
<template id="child">
<p>Compiled in {{scope}}</p>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.26/vue.min.js"></script>
In this case i'd use local registration for the child component. Instead of making it global you can register it locally like:
// extend and register in one step
Vue.component('my-component', {
template: '<div>A custom component!</div>'
})
// also works for local registration
var Parent = Vue.extend({
components: {
'my-component': {
template: '<div>A custom component!</div>'
}
}
})
You don’t have to register every component globally. You can make a component available only in the scope of another component by registering it with the components instance option:
http://vuejs.org/guide/components.html#Local-Registration

Re-render a Vue.js component

Is there a way to re-render a Vue.js component? I have a dropdown list and a form. Both of which are seperate components, one is located at the header and another one one somewhere in the middle of the page.
Both are seperate components and I can't make one a child component of another.
So is it possible to trigger a re render of the dropdown in Vuejs ?
If we assume that the project list to the list component is sent to the component via a prop, it's fairly trivial to update the main app's list, which in hand will update the list component.
Quick example of how it would work:
<div id="app"> <!-- The app -->
<list :projects="projectsList"></list> <!-- list component -->
<add-project></add-project> <!-- project add component -->
</div>
<template id='project-list-template'> <!-- template to list projects -->
<div v-for="proj in projects">{{proj}}</div>
</template>
<template id="add-project-template"> <!-- template to add project -->
<input v-model='projectData.name'/><button v-on:click="saveProject()">Save</button>
</template>
<script>
// Init components
var ProjectListComponent = Vue.extend({
props: ['projects'],
template: '#project-list-template'
});
var AddProjectComponent = Vue.extend({
template: '#add-project-template',
data: function() {
return {
projectData: {
name:'test'
}
}
},
methods: {
saveProject:function() {
this.$root.appendProjectToList(this.projectData); // Target the app's function
}
}
});
// Register components
Vue.component('list', ProjectListComponent);
Vue.component('add-project', AddProjectComponent);
// Do app stuff
new Vue({
el: '#app',
data: {
projectsList: ['ProjectX','ProjectY']
},
methods: {
appendProjectToList: function(project) {
this.projectsList.push(project.name);
}
}
});
</script>
If this is not the case, you should REALLY add a simplified code example.