Im relatively new with VueJS, and I've got no clue about how to make some data globally available. I would like to save data like API endpoints, user data and some other data that is retrieved from the API somewhere where each component can get to this data.
I know I can just save this with just vanilla Javascript but I suppose there is a way to do this with VueJS. I may be able to use the event bus system to get the data but I don't know how I can implement this system to my needs.
I would appreciate it if somebody can help me with this.
Make a global data object
const shared = {
api: "http://localhost/myApi",
mySharedMethod(){
//do shared stuff
}
}
If you need to expose it on your Vue, you can.
new Vue({
data:{
shared
}
})
If you don't, you can still access it inside your Vues or components if you've imported it or they are defined on the same page.
It's really as simple as that. You can pass shared as a property if you need to, or access it globally.
When you're just starting out there is no real need to get complicated. Vuex is often recommended, but is also often overkill for small projects. If, later, you find you need it, it's not that hard to add it in. It's also really for state management and it sounds like you just really want access to some global data.
If you want to get fancy, make it a plugin.
const shared = {
message: "my global message"
}
shared.install = function(){
Object.defineProperty(Vue.prototype, '$myGlobalStuff', {
get () { return shared }
})
}
Vue.use(shared);
Vue.component("my-fancy-component",{
template: "<div>My Fancy Stuff: {{$myGlobalStuff.message}}</div>"
})
new Vue({
el: "#app"
})
Now, every Vue you create and every component has access to it. Here is an example.
You can use Store which will hold your application state.
const store = new Vuex.Store({
state: {
userData: []
},
mutations: {
setUserData (state, data) {
state.userData = data
}
}
})
With this you can access the state object as store.state, and trigger a state change with the store.commit method:
store.commit('setUserData', userData)
console.log(store.state.userData)
Vue Mixin
// This is a global mixin, it is applied to every vue instance.
// Mixins must be instantiated *before* your call to new Vue(...)
Vue.mixin({
data: function() {
return {
get $asset() {
return "Can't change me!";
}
}
}
})
template
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.3/vue.js"></script>
<div id="app">
In Root: {{globalReadOnlyProperty}}
<child></child>
</div>
Or
Vue.prototype.$asset = 'My App'
I just use an environment.js file to store all of my endpoints as object properties.
var urls = {};
urls.getStudent = "api/getStudent/{id}";
etc...
Then I put reference to this environment.js file in the head of document on pages where I have VueJS code that needs access to those endpoints. Im sure there are many ways to do this.
Related
I am navigating into a view from another view and passing the itemdId as param value to vue router.
I want to be able to call firebase with that itemId so that I can filter the data and the filtered result/data is used in the UI. I am using vuefire.
What is happening is that vue starts rendering before the data is available in created() and I see error is the console saying view is referring to undefined property values.
Is there a way to render the view after the data is available?
I have tried the using beforeMount as well as created and beforeCreate approaches.
See code below:
<h1>{{projects[0].project_name}}</h1> //it says type error. cannot read property project_name of undefined. Sometimes on refresh it works but most times it does not.
Script code below:
let projectsRef = db.ref('projects');
export default {
name: 'ProjectDetailsOpenInvesting',
props: {
data: Object
},
firebase:{
projects: projectsRef
},
data(){
return {
projects:[],
.....
}
},
created(){
var that = this;
console.log(this.$route.params.itemid) //this works
this.projects = this.projects.filter(function(p){
return p.project_id == that.$route.params.itemid //this works as well
})
}
Firebase screenshot here
As you have mentioned, one approach is to fetch after navigation, i.e. fetch the data in the component's created hook.
To do that with vuefire you need to programatically bind the Realtime Database projectsRef Reference to the projects property in your Vue application, as follows:
created(){
console.log(this.$route.params.itemid)
const itemId = this.$route.params.itemid;
this.$rtdbBind('projects', projectsRef.orderByKey().equalTo(itemId)).then(projects => {
this.projects === projects;
});
}
As explained in the API doc:
$rtdbBind returns a Promise that is resolved once the data has been
retrieved and synced into the state.
Note that you need to install the rtdbPlugin plugin: https://vuefire.vuejs.org/api/vuefire.html#rtdbplugin
Also note that instead of filtering the desired project item in the front-end (with filter(function(p){return p.project_id == that.$route.params.itemid}))), we filter it in the back-end, at the level of the database (projectsRef.orderByKey().equalTo(itemId)) which is more efficient and which avoids transmitting the entire set of objects from the back-end to the front-end (and avoids paying for this downloaded volume, see https://firebase.google.com/pricing?authuser=0).
Let's say I have a templateA.html with input fields and then another separate templateB.html which is supposed to display the values of the input fields from templateA.html as they are typed in. I've gotten it to work with Session.set and Session.get but the problem is the input values stay the same when the page is refreshed and it doesn't seem the best way to do this. I then tried to use ReactiveVar but since I can't find any good examples on how to use it I'm a bit lost. The following is how it works with Session so maybe this will help understand what I'm trying to do with ReactiveVar.
Template.templateA.events({
'change #batch_form': function(){
var batch_num = document.getElementById('batch_number').value;
var dist_name = document.getElementById('distributor_name').value;
var data = {
batch_number: batch_num,
dist_name: dist_name
}
Session.set('form_data', data);
}
})
Template.templateB.helpers({
input_data(){
return Session.get('form_data');
},
});
TemplateB.html
<template name='templateB'>
<p>Batch #:{{input_data.batch_number}}</p>
<p>Batch #:{{input_data.dist_name}}</p>
</template>
You should avoid Session where you can. Better scope your Template-exceeding variables with a shared scope. ES6 import/export are suitable for that.
Consider you want to share a ReactiveDict (which behaves like Session) as state between only those two Templates. You can create a new js file with the following content:
shared.js
import { ReactiveDict } from 'meteor/reactive-dict'
export const SharedAB = new ReactiveDict()
This gives you the control to share the state only between those Templates, that import the object.
templateA.js
import { SharedAB } from './shared.js'
Template.templateA.events({
'change #batch_form': function(){
var batch_num = document.getElementById('batch_number').value;
var dist_name = document.getElementById('distributor_name').value;
var data = {
batch_number: batch_num,
dist_name: dist_name
}
SharedAB.set('form_data', data);
}
})
templateB.js
import { SharedAB } from './shared.js'
Template.templateB.helpers({
input_data(){
return SharedAB.get('form_data');
},
});
if they don't have any parent-child relationship you can use common ReactiveDict variable(here Session) to pass the data between just like you're doing.
but the problem is the input values stay the same when the page is
refreshed and it doesn't seem the best way to do this
for this on onDestroyed callback of template you can clear the Session variable values
Template.templateA.onDestroyed(function(){
Session.set('form_data', false); // or {}
})
so when you come back to this page/template, there is no data to display.
The thing i like about realtime databases like Firebase is their push features. for example vuefire makes it incredibly simple to have self-updating objects without stringing up hundreds of socket listeners and callbacks.
This is an example:
data(){
return {
books: []
}
},
created(){
axios.get('/books', (books) => {
this.books = books
})
}
In order to add db reactivity to this, I need at least 3 events “book-created”, “book-updated”, “book-removed”.
Pusher.on('book-created', (book) {
this.books.push(book)
})
Pusher.on('book-updated', (book) {
let b = _.find(this.books, {id: book.id})
if(b){
_.extend(b, book)
}
})
etc..
It gets quite hairy when you have many models and many views, some views have a different set of books so I can’t just have one standard books vuex variable. Is there a sensible/fast way to approach this?
I can certainly abstract the event sending on my backend so all my models push events automatically. but the front-end is where i’m confused.
I never worked with pusher before, and I don't know exactly what's the "different set of books" you're referring to, but I think I can give you some starting point to make your code look a bit more elegant.
The first thing I suggest; do not try to reinvent the wheel, use component mixins. Just create a mixin like you'd normally consume your backend, make it work with some dynamic options (that you can set in the component's data()) and import it on every component you need the same functionality, with different options.
Example (you'll need to adapt it accordingly)
// Create an ES6 module and export this function, or the whole mixin
// (in that case export the mixin instead)
const initPusher = function ({pusher, collection, key, events}, component) {
collection = component[collection]
pusher.on(events.created, (record) => {
collection.push(record)
})
pusher.on(events.updated, (record) => {
let r = _.find(collection, {[key]: record[key]})
if(r){
_.extend(r, record)
}
})
}
const PusherMixin = {
methods: {
// You can add some common methods for all your
// components making use of this mixin
},
created () {
initPusher(Object.assign({}, {
pusher: Pusher,
collection: 'collection',
key: 'id',
events: {
created: 'book-created',
updated: 'book-updated'
}
}, this.pusherOptions), this)
}
}
new Vue({
el: "#app",
mixins: [PusherMixin],
data () {
return {
books: [],
pusherOptions: {
collection: 'books',
key: 'id',
events: {
created: 'book-created',
updated: 'book-updated'
}
}
}
}
})
My second suggestion implied a manipulated array prototype with some custom methods and hooks to trigger events and XHRequests but after finishing the code for the mixin, I found out that extending the array's prototype with more methods was adding too much complexity, and a good mixin was totally the opposite of that.
If you want to go deeper, you may want to pay a look to RxJS and observables. You can build something on top of that with Pusher to get something like a Firebase real time object.
Tutorial:
https://blog.pusher.com/building-realtime-applications-with-cyclejs-and-rxjs/
RxJS: http://reactivex.io/rxjs/
Official implementation for Vue:
https://github.com/vuejs/vue-rx
I have a project that I've been working on for awhile that I might want to use vuejs for some of the UI elements. I fired up a tutorial and tried to piece together a basic example.
I have a basic javascript object like so:
var hex = {};
hex.turn_phase = 'unit_placement';
On my template, I have:
var app = new Vue({
el: '#vue-test',
data: {
message: 'Hello Vue!',
turn_phase: hex.turn_phase,
},
delimiters: ["<%","%>"],
mounted: function() {
this.fetch_turn_phase();
},
methods: {
fetch_turn_phase: function() {
Vue.set(this, 'turn_phase', hex.turn_phase);
},
}
});
This renders the correct turn_phase on the template initially, but if I change the hex.turn_phase in the browser console, the template doesn't react.
Is there something that I missed in this basic example?
It looks like you may have made things unnecessarily difficult. Just access your Vue instance via app?
Always make sure you go through the setters generated by Vue.js. These are watched and will re-render your component.
Instead, try using
app.turn_phase = 'unit_placement';
You can get a better understanding here, Reactivity
Vue creates all the data variables, computed properties and values returned from methods reactive. in your case since you
are changing hex, which is not a vue variable, so vue will not detect any changes in this variable. However if you change message variable, it will be reflective and will be changed in the template.
I'm using filter function for internationalization like this:
<div>{{ "hello" | message }}<div>
message is a filter function that depends on global Vue.config.lang variable.
It works great, but if I change Vue.config.lang, message doesn't rerender.
I wanted to make message rerender anytime Vue.config.lang changes, so I changed my filter function from
message => locale_messages[Vue.config.lang][message]
to
message => new Vue({
template: '{{ message }}',
computed: {
message() { return locale_messages[Vue.config.lang][message]; }
}
})
But it doesn't work. Getting this error:
Uncaught TypeError: Converting circular structure to JSON
at Object.stringify (<anonymous>)
....
Is there anything I can do to make it work? I'm new to Vue.js and can't find a working solution.
Like Evan says, Filters should be pure, so thay can't use a global variable as key to get values from externals arrays. Because of side effects.
So, there is three solutions at your problem that comes in my mind :
Replace filters by methods.
Use vue-i18-n, a simple and powerful module for translation
Use a store system (vuex) wich provides you getters, and helps you manage a global state.
Personnaly I love to use vuex and vue-i18-n together.
In that way I can centralize my data and the language in use. I can also serve specific data in several languages using the store, and let vue-i18-n cares about all the strings in the project.
New to Vue myself, so not quite sure how global variables work, but you can definitely pass params to a custom filter - even a Vue reference. You can do this:
<!-- this = a reference to the vue instance -->
<span class="display-3">{{value|FormatValue(this)}}</span>
[...]
props: ["aIsPercentNeeded"],
[...]
Vue.filter("FormatValue", function (aValue, aVueInstance)
{
if (!aVueInstance["aIsPercentNeeded"])
{
return aValue;
}
return aValue + "%";
});