VUE add a component with JS - vue.js

Can I create/mount a VUE component calling a JS function in a fullyloaded page?
As a async call? Something like:
function getComponent(obj){
return <component parameters="obj"></component >;
}

Can I create/mount a VUE component calling a JS function in a fullyloaded page?
I think you want to load/mount the component in a certain condition and not on page initialization.
If that the case then you can take advantage of lazy-loading and dynamic components:
<template>
//...
<button #click="activateComponent">Activate component</button>
<component :is="dynamicComponent" />
//...
</template>
<script>
export default {
components: {
MyCmp: () => import('./MyCmp.vue') //lazy loading
},
data: () => ({
dynamicComponent: null
}),
methods: {
activateComponent () {
this.dynamicComponent = 'MyCmp'
}
}
}
</script>

I think what you are looking for is Vue dynamic components. What that allows you is to choose which component to load after the original component has loaded. You can do that whenever and however you want to, aka in an async way. You can read more in the Vue guides. All the code you need is there so won't repost it here again.

Related

Call a function from another component using composition API

Below is a code for a header and a body (different components). How do you call the continue function of the component 2 and pass a parameter when you are inside component 1, using composition API way...
Component 2:
export default {
setup() {
const continue = (parameter1) => {
// do stuff here
}
return {
continue
}
}
}
One way to solve this is to use events for parent-to-child communication, combined with template refs, from which the child method can be directly called.
In ComponentB.vue, emit an event (e.g., named continue-event) that the parent can listen to. We use a button-click to trigger the event for this example:
<!-- ComponentB.vue -->
<script>
export default {
emits: ['continue-event'],
}
</script>
<template>
<h2>Component B</h2>
<button #click="$emit('continue-event', 'hi')">Trigger continue event</button>
</template>
In the parent, use a template ref on ComponentA.vue to get a reference to it in JavaScript, and create a function (e.g., named myCompContinue) to call the child component's continueFn directly.
<!-- Parent.vue -->
<script>
import { ref } from 'vue'
export default {
⋮
setup() {
const myComp = ref()
const myCompContinue = () => myComp.value.continueFn('hello from B')
return {
myComp,
myCompContinue,
}
},
}
</script>
<template>
<ComponentA ref="myComp" />
⋮
</template>
To link the two components in the parent, use the v-on directive (or # shorthand) to set myCompContinue as the event handler for ComponentB.vue's continue-event, emitted in step 1:
<template>
⋮
<ComponentB #continue-event="myCompContinue" />
</template>
demo
Note: Components written with the Options API (as you are using in the question) by default have their methods and props exposed via template refs, but this is not true for components written with <script setup>. In that case, defineExpose would be needed to expose the desired methods.
It seems like composition API makes everything a lot harder to do with basically no or little benefit. I've recently been porting my app to composition API and it required complete re-architecture, loads of new code and complexity. I really don't get it, seems just like a massive waste of time. Does anyone really think this direction is good ?
Here is how I solved it with script setup syntax:
Parent:
<script setup>
import { ref } from 'vue';
const childComponent = ref(null);
const onSave = () => {
childComponent.value.saveThing();
};
</script>
<template>
<div>
<ChildComponent ref="childComponent" />
<SomeOtherComponent
#save-thing="onSave"
/>
</div>
</template>
ChildComponent:
<script setup>
const saveThing = () => {
// do stuff
};
defineExpose({
saveThing,
});
</script>
It doesn't work without defineExpose. Besides that, the only trick is to create a ref on the component in which you are trying to call a function.
In the above code, it doesn't work to do #save-thing="childComponent.saveThing", and it appears the reason is that the ref is null when the component initially mounts.

Lazy loading a specific component in Vue.js

I just make it quick:
In normal loading of a component (for example "Picker" component from emoji-mart-vue package) this syntax should be used:
import {Picker} from "./emoji-mart-vue";
Vue.component("picker", Picker);
And it works just fine.
But when I try to lazy load this component I'm not sure exactly what code to write. Note that the following syntax which is written in the documentation doesn't work in this case as expected:
let Picker = ()=>import("./emoji-mart-vue");
The problem, I'm assuming, is that you're using
let Picker = ()=>import("./emoji-mart-vue");
Vue.component("picker", Picker);
to be clear, you're defining the component directly before the promise is resolved, so the component is assigned a promise, rather than a resolved component.
The solution is not clear and depends on "what are you trying to accomplish"
One possible solution:
import("./emoji-mart-vue")
.then(Picker=> {
Vue.component("picker", Picker);
// other vue stuff
});
This will (block) wait until the component is loaded before loading rest of the application. IMHO, this defeats the purpose of code-spliting, since the application overall load time is likely worse.
Another option
is to load it on the component that needs it.
so you could put this into the .vue sfc that uses it:
export default {
components: {
Picker: () => import("./emoji-mart-vue")
}
};
But this would make it so that all components that use it need to have this added, however, this may have benefits in code-splitting, since it will load only when needed the 1st time, so if user lands on a route that doesn't require it, the load time will be faster.
A witty way to solve it
can be done by using a placeholder component while the other one loads
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: SomeLoadingComponent
});
Vue.component("picker", Picker);
or if you don't want to load another component (SomeLoadingComponent), you can pass a template like this
const Picker= () => ({
component: import("./emoji-mart-vue"),
loading: {template:`<h1>LOADING</h1>`},
});
Vue.component("picker", Picker);
In PluginPicker.vue you do this:
<template>
<picker />
</template>
<script>
import { Picker } from "./emoji-mart-vue";
export default {
components: { Picker }
}
</script>
And in comp where you like to lazy load do this:
The component will not be loaded until it is required in the DOM, which is as soon as the v-if value changes to true.
<template>
<div>
<plugin-picker v-if="compLoaded" />
</div>
</template>
<script>
const PluginPicker = () => import('./PluginPicker.vue')
export default {
data() = { return { compLoaded: false }}
components: { PluginPicker }
}
// Another syntax
export default {
components: {
PluginPicker: () => import('./PluginPicker.vue')
}
}
</script>

Is `async/await` available in Vue.js `mounted`?

I'd like to do something like this in mounted() {}:
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
So I wonder if this works:
async mounted() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
},
In my environment it raises no errors, and seems to work well.
But in this issue, async/await in lifecycle hooks is not implemented.
https://github.com/vuejs/vue/issues/7209
I could not find further information, but is it available in fact?
It will work because the mounted hook gets called after the component was already mounted, in other words it won't wait for the promises to solve before rendering. The only thing is that you will have an "empty" component until the promises solve.
If what you need is the component to not be rendered until data is ready, you'll need a flag in your data that works along with a v-if to render the component when everything is ready:
// in your template
<div v-if="dataReady">
// your html code
</div>
// inside your script
data () {
return {
dataReady: false,
// other data
}
},
async mounted() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
this.dataReady = true;
},
Edit: As stated in the documentation, this is an experimental feature and should not be used in production applications for now.
The correct way to do this in vue3 would be to make your setup() function async like this:
<script>
// MyComponent.vue
export default defineComponent({
/* ... */
async setup() {
await fetchData1();
await fetchData2UsingData1();
doSomethingUsingData1And2();
this.dataReady = true;
}
}
</script>
And then use a suspense component in the parent to add a fallback like this:
<template>
<Suspense>
<template #default>
<MyComponent />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
So you would see the #fallback template while the component is loading, and then the component itself when it's ready.
Just use $nextTick to call async functions.

Emitting with component :is

I am building a dashboard app where users will be able to choose widgets that appear in certain positions. I am doing this using
<component :is="name-of-component">
Its all working well but I want the user to be able to edit the component and emit the changes to the parent. By editing lets say for example 1 component displays a welcome message which the user can change.
Right now I have the following
Dashboard.vue
<template>
<component :is="name-of-component"></component>
</template>
<script>
data () {
return {
name-of-component: 'selected-component-name'
}
}
</script>
In the script is the computed, mounted etc. which I don't think have relevance to the question.
Since Im using component :is Im finding it tricky to pass props and emit changes. In my store I have 2 props for the component (title & subtitle) in an array. I can hardcode :props="welcomeMessage" but I don't want to hard code since Im using :is and position of widgets can change.
Emit is also causing an issue for me. I can, of course, emit by hard coding the call to the component but since Im using :is its not going to work for me.
Heres what is working but I need to make it dynamic as any component :is wan contain any widget. Any ideas?
<component
:is="welcomeMessage"
:props="dashboard.welcomeMessage"
#update-welcomeMessage="welcomeMessage(e)">
</component>
OR
<component
:is="busStops"
:props="dashboard.myBusStop"
#update-busStop="busStop(e)">
</component>
Id like to have the components so that I can pull in the different concerns and have each one be more like this where "name-of-component" could be used to populate the :is, :props and #update:
<component
:is="name-of-component"
:props="dashboard.name-of-component"
#update-name-of-component="name-of-component(e)">
</component>
You can use the v-bind and v-on capabilities, and use computed properties just like you are already doing it. I'll explain myself:
<some-component #some-event="doThis" foo="bar"></some-component>
is the same as writing:
<some-component v-bind="{foo: 'bar'}" v-on="{'some-event': doThis}"></some-component>
That means that you can write computed properties to compute which listeners and attributes you want to use for your dynamic component.
I wrote a complete example on jsFiddle if you want: https://jsfiddle.net/tsc2pmrx/
Template:
<div id="app">
<component :is="componentToUse" v-on="listeners" v-bind="attributes"></component>
</div>
JS:
Vue.component('greeting', {
props: ['name'],
template: '<h1>Welcome {{ name }} !</h1>',
mounted () {
setTimeout(() => {
this.$emit('some-event')
}, 2000)
}
});
Vue.component('other-component', {
template: '<h1>Welcome to Other Component</h1>'
})
// create a new Vue instance and mount it to our div element above with the id of app
var vm = new Vue({
el: '#app',
data: {
componentToUse: 'greeting'
},
methods: {
handleOtherComponentEvent () {
console.log('Hello World from other component listener')
},
handleGreetingComponentEvent () {
console.log('Hello World from greeting component event listener')
}
},
computed: {
listeners () {
if (this.componentToUse === 'greeting') {
return {
'some-event': this.handleOtherComponentEvent
}
} else if (this.componentToUse === 'other-component') {
return {
'some-greeting-event': this.handleGreetingComponentEvent
}
}
return {}
},
attributes () {
if (this.componentToUse === 'greeting') {
return {
'name': 'Hammerbot'
}
}
return {}
}
}
});

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