Vue props is undefined when print component dynamically - javascript

My problem is on the dynamically creation of the TAG "galeriaimages".
Vue works fine but the props are always undefined
thanks for all.
main.js
import Vue from 'vue'
import Gi from './components/galeriaimages.vue'
import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
document.addEventListener('DOMContentLoaded', function() {
new Vue({vuetify, render: h => h(Gi) }).$mount('galeriaimages');
});
HTML
<galeriaimages p1="awesome" /> <!-- I create it dinamically-->
Vue component
<script>
export default {
props: ['p1'] ,
data: function() {
return {
}
},
created: function() {
alert(this.p1); //this is always undefined
}
}
Thanks to #skirtle for give me the answer :-)
I added this line in my vue.config.js
runtimeCompiler: true
...and all works fine

The bit where you write h(Gi) is creating a galeriaimages component but not passing any props to it.
To pass the prop you would need to write:
new Vue({
vuetify,
render: h => h(Gi, {props: {p1: 'awesome'}})
}).$mount('galeriaimages');
However, I suspect that isn't what you're really trying to do.
You currently seem to be mounting directly to the <galeriaimages> element, which is a bit odd but if you remove the render function it should work. You can also use el instead of $mount:
new Vue({
vuetify,
components: {galeriaimages: Gi},
el: 'galeriaimages'
});
I would add that the reason most examples use a render function for the root Vue instance is that it avoids the need to include the template compiler in the Vue build. This only works if all your other Vue components are pre-built .vue files. If you have any templates at runtime, including those in your HTML, then you'll need to include the template compiler anyway. In that scenario there's no benefit to using a render function on the root instance.

You need to provide the component matching the tag <galeriaimages>. Your custom render function is overriding the template parsing, so it is not parsing the <galeriaimages> as a component tag.
new Vue({vuetify, components: {galeriaimages: Gi} }).$mount('galeriaimages');
Also your components are not creating any elements. They are not able to mount.

Related

How can I access/mutate Vue component properties from vanilla JS

I have a Vue 2 project made with Vue CLI, and I plan to distribute it as a library, ideally with the dependencies and Vue syntax stuff abstracted away by some kind of wrapper script. I would like to allow this kind of interaction:
// mount the component on a plain JS webpage
const myComponent = new MyComponent('#my-component');
// handle events from the component in vanilla JS
myComponent.on('load', someHandler);
// (A.) call a component method and get a return value
const processedData = myComponent.process(123);
// (B.) access/mutate reactive component data properties
myComponent.setMessage('Hello world!');
I have tried changing the "build target" to build a Libary or a Web Component as mentioned in the Vue documentation. I can mount the library component just fine, and handle events, but it doesn't mention how I might interact with the component data from outside the Vue VM (see comments A and B).
How can I access Vue component methods and data properties from outside the Vue VM, in vanilla JS?
To access the Vue component properties (and methods) outside of the VM, you can mount it with a "template ref" like this:
const vm = new Vue({
components: {
MyComponent,
},
template: `
<my-component
ref="myComponent"
/>
`,
}).$mount('#mount-element");
and then you can call its methods like this:
vm.$refs.myComponent.someFunction();
You'll get the returned values and it will access/mutate reactive properties inside the VM as expected.
To use the class syntax described in the original question, we can create a simple class to wrap the vue component:
// import the component built by Vue CLI with the "library" build target
// (puts `MyComponent` in the global namespace)
import './MyComponent.umd.min.js';
import Vue from 'https://unpkg.com/vue#2/dist/vue.esm.browser.min.js';
export default class {
constructor(mountElement) {
// mount vue VM with a template ref to access its properties
const thisClass = this;
this.vm = new Vue({
components: {
MyComponent,
},
template: `
<my-component
ref="myComponent"
/>
`,
}).$mount(mountElement);
this.component = this.vm.$refs.myComponent;
}
// define methods that could call this.component's functions
someFunction() {
// do stuff
return this.component.someFunction()
}
}
It seems to work pretty well. A possible improvement would be to build the component library with a different tool, since Vue CLI v3 (with Vue v2 projects) can't output ESM module files, so the best we can do is a UMD modle that gets defined globally.

How to pass props to Vue from another js module?

Is it possible to pass props from any js module to vue?
Props are passing fine between components for me, but not from the actual Vue app itself:
main.js
import Vue from 'vue'
import App from './App.vue'
var myVue = new Vue({ export to other files
el: '#entry',
components: {App},
render: h => h(App),
data: function(){
return{
testSuccess:'this test was successful!'
}
},
})
window.myVue = myVue // we use window.myVue because if we can export to window, we can export to other js modules.
App.vue
<template>
<div ref="app">
{{ testSuccess ? testSuccess : 'prop not imported!' }}
</div>
</template>
<script>
export default = {
name: "app",
props: ["testSuccess"]
}
</script>
index.html
<div id="entry" >
<app :testSuccess="testSuccess"></app>
</div>
<script src="/dist/build.js"></script>
What am I missing?
I understand how to do this with components.
I want to be able to export the Vue module into other js modules and pass meaningful information to it.
This is the render function for your root Vue instance:
render: h => h(App)
You aren't passing any props to h, so the App will be created without props.
The template inside #entry will be ignored because you're providing an explicit render function.
So either:
Remove the render function from the root Vue instance. Note that the reason most examples use a render function is so that they can use the runtime-only build of Vue, which can't compile templates.
Remove the template from inside #entry and pass the props to App within the render function.
The latter would look like this:
render (h) {
return h(App, { props: { testSuccess: this.testSuccess } })
}
Note this can't use an arrow function because it requires access to this.
Once you're passing the props correctly you should be able to update the value using myVue.testSuccess = '...' no problem.
As you have discovered, you cannot pass props to your $root Vue app. However, you can modify the properties of the Vue instance and Vue will react to those changes.
In your example above, you could write anywhere (including the console):
window.myApp.testSuccess= "I've been changed!";
and the HTML should update.
However, the way you have written your components above mean that the testSuccess property is not being passed into the App.vue component. Instead of making your App.vue a component of the root Vue instance, create them like this:
index.html
<div id="app" >
</div>
<script src="/dist/build.js"></script>
main.js
import Vue from 'vue'
import App from './App.vue'
var myVue = new Vue({ // export to other files
el: '#app',
...App,
})
window.myVue = myVue // we use window.myVue because if we can export to window, we can export to other js modules.
App.vue
<template>
<div>
{{ testSuccess || 'testSuccess is blank!' }}
</div>
</template>
<script>
export default {
data: { // doesn't need to be a function in the root Vue instance
testSuccess: "this is the default text",
...
}
</script>
AN EVEN BETTER WAY
Despite all the above, an even better way is to use proper state management. By placing all your shared state into a dedicated state object (or VueX), any module which has access to the state object can manipulate the state.
Have a read of https://v2.vuejs.org/v2/guide/state-management.html

Is there a way to use RequireJS when importing VueJS components?

I have a project which uses RequireJS and Vue components. I am trying to modularise the Vue components into smaller parts and I want to be able to export smaller vue components so that they can be used by a bigger one. This is simple with standard vue:
import SomeComponent from './SomeComponent.vue'
new Vue({
el: '#app',
...
components: { SomeComponent },
...
}
but how would this be done with requireJS?
You can use require to import your *.vue components like this:
new Vue({
el: '#app',
...
components: {
'some-component': require('../path/to/components/SomeComponent.vue').default,
...
},
...
});
Your *.vue files should be structured like this:
<template>
<!-- Component HTML -->
</template>
<script>
export default {
name: 'some-component',
...
}
</script>
<style>
/* Component Styles */
</style>
As an alternative, you can use require to register you components globally (see api docs):
Vue.component('some-component', require('../path/to/components/SomeComponent.vue').default);
new Vue({
el: '#app',
... // no need to declare `SomeComponent` here
});
This is a good option if you have several components that will all make use of SomeComponent.
I'm not exactly certain what you are asking, but if you are just trying to modularize, you can do this with import. The way to do this is by importing components into components.
If you have a pagination component that is being imported to your app, you can import a smaller component (a sub-component) into pagination component. You can go as small as you want this way and have sub-sub-sub-components. The syntax for importing components into components is identical to importing components into a new vue page.
Unless I'm missing something, there's no reason to try and use require. Vue already gives you everything you need with the ability to import components. That's the proper way to modularize and keep your code DRY in Vue.

How to call all Custom Directive globally instead of calling each component?

I'm setting up a directive for my Vue project on separate individual files.
So far I have succeeded separating files and calling it globally but only possible to do it individually instead of exporting the entire directive and make it global.
**directive.js
const highlight ={
bind(el, binding, vnode) {
... some code
}
const highlight2 ={
... some code
}
export default {
highlight,
highlight
}
**main,js
import Vue from 'vue'
import App from './App.vue'
import * as directive from './directive.js'
Vue.directive(directive);
new Vue({
el: '#app',
render: h => h(App),
directive
})
so far I have been able to call this directive on my main.js but stuck on how to make it globally without calling every single component. like
import { highlight, highlight2} from './directive
Edit:
Found my way by looping through with forEach function.
Object.keys(directive).forEach(function(name){
Vue.directive(name, directive[name])
})

Using vue-router gives "Cannot find element #app" and "TypeError: Cannot read property 'matched' of undefined" errors

I am attempting to use Vue.js to recreate a frontend app I previously made using Scala Play (I'd like to learn more about component-based web design).
I had everything loading fine for the first page I made, but I wanted to implement routing so I could have multiple pages (rather than just having one big single-page application). I installed vue-router using npm install vue-router --save, and am trying to follow the following tutorial (using the components I've already made though): https://scotch.io/tutorials/how-to-build-a-simple-single-page-application-using-vue-2-part-1.
As far as I can tell, everything should be loading smoothly, but nothing loads and I get the following console errors:
[Vue warn]: Cannot find element: #app
[Vue warn]: Cannot find element: #app
[Vue warn]: Error in render: "TypeError: Cannot read property 'matched' of undefined"
found in
---> <App> at src/App.vue
<Root>
TypeError: Cannot read property 'matched' of undefined
at render (vue-router.esm.js?8c4f:76)
at createFunctionalComponent (vue.runtime.esm.js?2b0e:3033)
.... (etc)
I've not pasted the full stack traces but can do if needed.
My code is below:
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import Home from './components/Home.vue'
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Home }
]
const router = new VueRouter({
routes
})
new Vue({
el: '#app',
template: '<App/>',
components: { App },
router
}).$mount('#app')
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
background-color: aliceblue;
}
</style>
components/Home.vue
<template>
<div id="app">
<Header />
<Posts v-bind:posts="posts" />
</div>
</template>
<script>
import Posts from './Posts.vue'
import Header from './Header.vue'
export default {
name: 'home',
components: {
Header, Posts
},
data() {
return {
posts: []
}
}
}
</script>
<style>
</style>
Let me know if I need to include Header.vue and Posts.vue too - I've omitted them because they were rendering fine before I tried to use vue-router.
How do I get rid of these console errors so my home page renders correctly when using vue-router? I have searched around and so far, all of my search results have led me to some variation on "make sure router is lowercase" (it is), "make sure routes is lowercase" (it is), and "use import Vue from 'vue/dist/vue.js'" (didn't work).
With regard to the original issue:
As I am sure you know from the comment discussion, the errors [Vue warn]: Cannot find element: #app were caused because two Vue instances were configured to mount—one after another—at the same root DOM node #app.
The first Vue instance mounts, and in doing so, replaces the root DOM node with the HTML defined in its own template. Therefore, the #app root DOM node no longer exists, and the second Vue instance fails to mount because it cannot find any DOM node with id #app.
This implies that if the first Vue template included a DOM node with id #app, the second Vue instance would probably mount there, replacing any existing HTML from the first instance with its own.
This error is resolved by removing the duplicate Vue instance.
With regard to your follow-up question:
I don't have the faintest idea why this works without all the el: '#app', template: '<App/>', components: { App }
The first thing to note is that Vue instance property vm.$el and Vue instance method vm.$mount() are functionally equivalent for the most part—each allows you to specify the root DOM element where the Vue instance will be mounted.
The main difference is in their application:
You use vm.$el to declare the root DOM node at instance creation. Supplying a value is not obligatory however, and if you don't the instance is created in an unmounted state.
vm.$mount() allows you to specify a root DOM node for an unmounted instance after instantiation.
As stated in the docs:
If a Vue instance didn’t receive the el option at instantiation, it will be in “unmounted” state, without an associated DOM element. vm.$mount() can be used to manually start the mounting of an unmounted Vue instance.
Of course, if you use vm.$mount() instead of vm.$el at instance creation, this is largely a matter of developer taste (although $mount() does also accept a variety of inputs so there are other subtle differences there). But from the perspective of your question, there is no practical difference.
Finally, Vue's template property and render() method are simply two strategies for defining the template structure of a Vue instance:
template is the higher-level template abstraction that allows you to define a string template; e.g: <div><App/></div> which Vue parses into a structure, instantiating any child components as required.
render() is an alternative, lower-level template abstraction that is a closer-to-the-compiler alternative to templates. It is more flexible for finer-grained/programmatic use-cases or for use with other template languages such as JSX or raw JS.
The main obvious difference in your examples is that template defines a string template using the <App/> component. The Vue instance needs to know what this component is before this template string is parsed, so it must be declared as a component.
Whereas, render() allows the App component to be passed for render directly, so it is not required to define a template property or declare any components.
My outlook is that the former is more explicit—which is not a bad thing—and is more declarative and readable when declaring a structure with several components. To that end, Vue seems to recommend template strings for most usage. That said, the latter approach can also be neat, and you'll often see it where a single, top-level, entry-point component (e.g: App) is being mounted.
Another (implicit) strategy is the DOM template. In the absence of a template property or render() method, the HTML structure defined within the root DOM node is used—although this is generally discouraged.
Hope this helps :)
I fixed the issue by cropping down my main.js to only contain one new Vue section, which looks like this now:
new Vue({
router,
render: h => h(App)
}).$mount('#app')
I don't have the faintest idea why this works without all the el: '#app', template: '<App/>', components: { App } stuff though, so if that could be explained I'd appreciate it - I was hesitant to add an answer as I don't know why this worked; if someone adds an answer explaining it I'd be happy to accept that (or feel free to edit my answer with an explanation and cut this paragraph out).
I found that the issue could be missing
<div id="app"></div>
at the body of your index.html.

Categories

Resources