VueJS - Define components in secondary .js file - vuejs2

I have this situation: I have an Laravel app that is not an SPA. But I use Vue in many components of my application, using single-file components and all of that.
But I have some components that I only want to load in some pages, for performance reasons. But, when I register a component in a second .js file, I always get an error like this:
Property or method "addresses" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in )
My structure is something like this:
app.js
window.Vue = require('vue');
import ModalMessage from './../components/ModalMessage.vue';
import FlashMessage from './../components/FlashMessage.vue';
window.MainVue = new Vue({
el: '#app',
data: {
loading: false
},
components: { ModalMessage, FlashMessage }
});
All of this work's perfectly fine. But when I introduce a new .js file, I always get the error mentioned above.
Example:
account.js
window.UserAddresses = new Vue({
el: '#user-addresses',
data: {
addresses: []
},
methods: {
}
});
The HTML for the #user-addresses is:
<div id="user-addresses">
<table class="table">
<tr v-for="address in addresses">
<td>testing</td>
</tr>
</table>
</div>
And I always get the error:
Property or method "addresses" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
(found in )
In this internal page that generates the error, my JS files are included like this:
<script src="assets/js/app.js"></script>
<script src="assets/js/account.js"></script>
The account.js Vue instance is mounted before generates the error.
My question is: Is there a way to work with Vue in multiple .js files? I also didn't want to declare all my subcomponents data in my root Vue instance.
Is there a way to make this work?
UPDATE
I'm using webpack for compiling all files.

In general your top example looks just right and nothing I would see out of order. The problem though you describe with the addresses probably comes from how you use the UserAddresses.
I would guess you have it nested inside the #app element on the page.
The problem here is, you can't nest two Vue root instances in each other.
new Vue({...}) // get a Vue rootinstance
What you probably want to do is use a Component. You can use global components for this, and therefor it's fine to have one global root instance.
window.UserAddresses = Vue.component('user-addresses', {
data () {
return {
addresses: []
}
}
... // rest of your component code
}
You can get more information here: https://v2.vuejs.org/v2/guide/components.html

Related

Automatically initialising multiple Vue.js3 Single File Components each within its own Vue instance

I'd like to have multiple Vue3 components. Each as a separate Vue instance, and all automatically loaded.
Why? Because I want to use them on a page where there's potentially many other JS scripts that alter the page so I cannot change the DOM for them (by globally initialising Vue) and I want not to worry that they will mess with my Vue components - so mounting all my components to some one big Vue instance like <body id="app"> is not an option.
With Vue2 it was a bit easier but does not work with Vue3. Now after some fight I worked out a solution for Vue3 it works fine but it seems ugly in my eyes so I assume there's a better one :)
I'm using (laravel-mix - Webpack solution for building my files).
Here's my HTML file with the components embedded:
// index.php
<TestOne data-vue-component="TestOne" />
<TestTwo data-vue-component="TestTwo" />
<TestOne data-vue-component="TestOne" />
And here's my JS file for loading those components:
// index.js
import { createApp } from 'vue';
const vueComponents = document.querySelectorAll('[data-vue-component]');
if (vueComponents.length) {
vueComponents.forEach((elem) => {
const componentName = elem.getAttribute('data-vue-component');
const app = createApp(require(`./components/${componentName}/${componentName}.vue`).default);
app.mount(elem);
}
}
So I look for the Vue components using data attribute data-vue-component and then for each one found I get the component's name, create a Vue3 instance importing the component at the same moment. Finally I mount it and do the same with the next one.

Access higher level component (parent) properties from subsubcomponents (children of children)

I am working on something which requires access to a property injected at the top level of a VueJS application. I can access the property by using:
this.$parent.$attrs.propertyname
This works nicely however, I am now wanting to be able to access the same item from a further subcomponent which works the same as above with an additional $parent call. Is there a way I can call this item at the top level in another way?
I have looked at $root and tested using Vue $vm console calls in the browser but doesnt work so far. I suspect theres a really easy way of doing this but I can't find it at the moment.
I have tried:
adding a return method from top level and calling it
$root
setting a prototype outside the vue application at the top level: Vue.prototype.$varname = '' and then attempting to assign on created() to this.$varname = this.mypropertyname
I suspect I am close somewhere or there is something I have missed.
I did plenty of fiddling and I have my answer.
So basically, my application passes data into the app from Laravel at the tag level. I have an overall template file for the app which is where I now set the data with a setter to root.
I will explain with some code:
Laravel:
<v-app token="abcdefg">
<template></template>
</v-app>
Thats the declaration of data passed into the app
On template.vue:
<script>
export default {
name: "template",
created() {
this.$root.setToken(this.$parent.$attrs.token);
}
}
</script>
We access the data and use the setter placed on app.js (root) to assign the data to an accessible $root location
app.js code:
const app = new Vue({
data() {
return {token: ''}
},
moment,vuetify,
router, store,
el: '#app',
methods: {
setToken(token)
{
this.token = token;
},
getToken()
{
return this.token;
}
}
});
A getter and setter available for the whole app to access. I may restrict access to the setter function later on but for now I am happy it works. Suggestions on restricting this will be nice.
Its now accessible via: this.$root.getToken()

Vue.js: is it possible to have a SFC factory?

I'm using single-file-components in a larger project and I'm new to Vue.JS. I'm looking for a way to dynamically create components, especially their templates on-the-fly at run-time.
My idea is to create a "component factory" also as a SFC and to parameterize it with props, e.g. one for the template, one for the data and so forth. I get how this works for already specified SFC and that I can simply exchange them with <component v-bind:is= ..., however the task here is different. I need to compose a template string, which potentially is composed of other component instance declarations, on-the-fly and inject it in another SFC. The code below doesn't work unfortunately.
<template>
<div>
<produced-component/>
</div>
</template>
<style></style>
<script>
import Vue from 'vue'
export default {
props: {
template: { type: String, default: '<div>no template prop provided</div>' }
},
components: {
'produced-component': Vue.extend(
{
template: '<div>my runtime template, this I want to be able to compose on-the-fly</div>'
}
)
}
}
</script>
It says:
The following answer: https://stackoverflow.com/a/44648296/4432432 answers the question partially but I can't figure out how to use this concept in the context of single-file-components.
Any help is greatly appreciated.
Edit 2020.03.16:
For future reference the final result of the SFC factory achieved as per the accepted answer looks like this:
<template>
<component:is="loader"/>
</template>
<script>
const compiler = require('vue-template-compiler')
import Vue from 'vue'
export default {
props: {
templateSpec: { type: String, default: '<div>no template provided</div>' }
},
computed: {
loader () {
let compSpec = {
...compiler.compileToFunctions(this.templateSpec)
}
return Vue.extend(compSpec)
}
}
}
</script>
You can surely have a Factory component with SFC; though what you are trying to do is a very rare case scenario and seems more like an anti/awkward pattern.
Whatever, you are doing currently is right. All you need to do is - use Full build of Vue which include the Vue Template Compiler, which will enable you to compile template at run-time (on the browser when app is running). But remember that for the components written in SFC, they must be compiled at build time by vue-loader or rollup equivalent plugin.
Instead of using vue.runtime.min.js or vue.runtime.js, use vue.min.js or vue.js.
Read Vue installation docs for more details.
Assuming you are using Webpack, by default main field of Vue's package.json file points to the runtime build. Thus you are getting this error. You must tell webpack to use full bundle. In the configuration resolve the Vue imports to the following file:
webpack.config.js
module.exports = {
// ... other config
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
};

Vuejs - require is not defined

I am just playing around with vuejs router and try to load a component.
I used the sample code and changed foo
// Define some components
var Foo = Vue.extend({
template: require('./components/test.vue')
});
var Bar = Vue.extend({
template: '<p>This is bar!</p>'
});
// The router needs a root component to render.
// For demo purposes, we will just use an empty one
// because we are using the HTML as the app template.
var App = Vue.extend({})
// Create a router instance.
// You can pass in additional options here, but let's
// keep it simple for now.
var router = new VueRouter()
// Define some routes.
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
router.map({
'/foo': {
component: Foo
},
'/bar': {
component: Bar
}
})
// Now we can start the app!
// The router will create an instance of App and mount to
// the element matching the selector #app.
router.start(App, '#app')
I also tested it with
Vue.component('Foo', {
template: require('./components/test.vue')
})
In my test.vue i have
<template>
<h2>Test</h2>
</template>
But not as soon as i use require i get everytime the error Required is not defined in my dev tools.
What do i wrong here?
require is a builtin in the NodeJS environment and used in Grunt build environments.
If you also want to use it in a browser environment you can integrate this version of it: http://requirejs.org
(Author) This is outdated:
Use Browserify or Webpack as there is active support in the Vue community
http://vuejs.org/guide/application.html#Deploying_for_Production (dead link)
I personally used this repo of the Vue GitHub-org to get started quickly.
Edit:
This has moved on a bit in early 2018.
Deployment guide: https://v2.vuejs.org/v2/guide/deployment.html
'getting started' type repo: https://github.com/vuejs/vue-loader

How to access Vue.js data across multiple js files

I require two js files in my app. the first(app.js) is to be general js file that all pages/views would use. The second(page1.js) is to be a page/view specific js file.
I instantiate a new Vue instance in app.js like so:
new Vue({
el : '#app',
data: {
balance: 0
},
ready: function() {
},
methods : {
},
})
Next, in the page1.js file I instantiate another Vue instance like so:
new Vue({
el : '#headerSection',
data: {
},
ready: function() {
console.log(this.balance)
},
methods : {
},
})
However, when I try to access the balance data object I get an error of undefined.
In the html I require the app.js file before the page1.js
I suspect my second instantiation of the Vue Instance is overwriting the first instance.
How do I go about separating the Vue elements over two js files?
You probably want to use the Vue component system instead of multiple independent View Models:
http://vuejs.org/guide/index.html#Components
This allows you to create a hierarchy of View Models that can interact with each other quite easily. You can put the javascript for them in multiple files without any problems.
In your particular case, you are creating two separate View Models that are overlapping in the same DOM but are otherwise not related to each other. The first one will compile all of the DOM below it and the second one will have no effect (or at least that's what seems to be happening).