How to isolate data from Vue component in JS class? - javascript

I have a simple JS class where in the constructor I initialize dictionary. I have methods to update or delete items from this dictionary.
export default class Items {
constructor() {
this.items = {};
//Code that adds some values to it
}
save(item) {
this.items[item.id] = item;
}
delete(itemId) {
delete this.items[itemId];
}
}
And I have a Vue component where I simply output values from this dictionary with
<p v-for="item in items">{{item}}</p>
In mounted method, I create Items object and assign items dictionary to variable items from Vue data. And from Vue I call methods save or delete, I see that dictionary items in Vue data is being changed, but UI doesn't get updated. I know that I can use Vue.$delete or Vue.$set but is it possible to isolate simple JS class from Vue and keep all the data in JS class?
I'm using Vue 2.4.4

Try to use power of VueJs
...
save(item) {
Vue.set(this.items, item.id, item);
}
delete(itemId) {
let index = this.items.findIndex(p => p.id === itemId);
Vue.delete(this.items, index);
}

Is there a reason to not let Vue keep the data? It will be able to monitor it to make the most effective dom changes that way. So in proper Vue style, you should move the class logic to the component level so that that data returns the items dictionary. Then attach the save and delete as methods for the component.
For objects you only get reactivity when you edit keys that existed at render time, otherwise, you need to alert Vue a key is being added. This is done with Vue.set(this.items, item.id, item);.
If you want to separate the logic from the component you can look into vuex which allows you to create modules of state and mutations.

Related

How to have data shared between Vue3 single file component instances?

I don't need to pass data between a parent component to a child one or the opposite, I need something like php/c static variables.
I want my sfc (single file component) to have some data that is shared among all instances in in the page.
As far as I understand that's why in sfc we define data as a function
export default {
data(){
return {
// props here
};
}
}
while in page scripts we can define it as an object
const app = new Vue({
data: {
// props here
},
}
That's because since we can have multiple instances of a sfc in the page defining its data as a function make each instance to execute in and get its own data, while with page script we can have a singe instance.
I need to define some of my sfc data to be shared between component instances, while other data to be per-instance.
Is there a way to do this?
That depends on the data to be defined, its complexity, and purpose.
If these are 2 or 3 readonly variables, they can be set as global properties using Vue.prototype (Vue 2) or app.config.globalProperties (Vue 3). I'm not sure, because in your example you use Vue 2 syntax.
If the data should be reactive, you can set up a simple state management as explained in the Vue documentation: Simple state management.
If the data is more complex than that, the next step will be Vuex.
Following #Igor answer I looked after the simple state management and found the ref() method that creates reactive primitive values.
In my specific use case I needed to share among all the sfc instances just an array, so in my sfc I had:
const reactive_array = ref([]);
export default {
data() {
return {
shared_array: reactive_array,
};
},
};

Can I push objects into Vue component data? Wanting to make a table after js filtering/manipulation

I want to make a table. Actually, I am making a website with many pages with many tables, so I wanted to make a table component. The table data has not yet been put into the table because I need to manipulate the data a lot in js.
When js is done with it, I intended to push every row object into the data property of my Vue Component (to then do a v-for in the html to fill the table).
but I cant find anyone pushing data into vue components. Are the examples right under my nose?
if I don't push into components themselves, that means I need to push into the parent vm? which means a new data property per table instance ..?
I am really struggling putting together the bigger picture connection when it comes to connecting Vue with the outputs from js... Looking for any input
Pushing data into a vue component is not a good practice. You would have a lot easier time if you use Vuex for what you're trying to do. Then you could build a closed-loop data system that updates state data in Vuex with mutations and returns the update to component with a getter. Doing all of this within component is possible if you initialize your data property correctly, though.
To actually answer your question, though, you would do it like this:
data () {
return {
myData: [],
someDataObject: null
}
}
...
methods: {
fillData () {
this.myData.push(this.someDataObject);
}
}
And in template:
...
<div v-for(item in myData, index) :key:item item:item>
<input type:'text' v-model="someDataObject">
<button #click="fillData();"></button>
<p>{{myData[0]}}</p>
</div>
...

VueJS XHR inside reusable component

Asking for best practice or suggestion how to do it better:
I have 1 global reusable component <MainMenu> inside that component I'm doing XHR request to get menu items.
So if I place <MainMenu> in header and footer XHR will be sent 2 times.
I can also go with props to get menu items in main parent component and pass menu items to <MainMenu> like:
<MainMenu :items="items">
Bet that means I cant quickly reuse it in another project, I will need pass props to it.
And another way is to use state, thats basically same as props.
What will be best option for such use case?
If you don't want to instantiate a new component, but have your main menu in many places you can use ref="menu" which will allow you to access it's innerHTML or outerHTML. I've created an example here to which you can refer.
<div id="app">
<main-menu ref="menu" />
<div v-html="menuHTML"></div>
</div>
refs aren't reactive so if you used v-html="$refs.menu.$el.outerHTML" it wouldn't work since refs are still undefined when the component is created. In order to display it properly you would have to create a property that keeps main menu's HTML and set it in mounted hook:
data() {
return {
menuHTML: ''
}
},
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}
This lets you display the menu multiple times without creating new components but it still doesn't change the fact that it's not reactive.
In the example, menu elements are kept in items array. If the objects in items array were to be changed, those changes would be reflected in the main component, but it's clones would remain unchanged. In the example I add class "red" to items after two seconds pass.
To make it work so that changes are reflected in cloned elements you need to add a watcher that observes the changes in items array and updates menuHTML when any change is noticed:
mounted() {
this.menuHTML = this.$refs.menu.$el.outerHTML;
this.$watch(
() => {
return this.$refs.menu.items
},
(val) => {
this.menuHTML = this.$refs.menu.$el.outerHTML;
}, {
deep: true
}
)
}
You can also watch for changes in any data property with:
this.$refs.menu._data
With this you don't need to pass props to your main menu component nor implement any changes to it, but this solution still requires some additional logic to be implemented in it's parent component.

Vue2 passing arbitrary named variable as prop

I am new to Vue and after checking the docs I can not figure out how to achieve the following:
pass an arbitrarily named variable as a prop to a component instance.
From my understanding, props are meant to be a way to allow data to be passed to a component and as it states on the website:
Passing Data to Child Components with Props:
Props are custom attributes you can register on a component. When a value is passed to a prop attribute, it becomes a property on that component instance.
Since props can be required, it would seem that we can design components under the assumption that some data would be there, and possible within certain parameters (if the validator option is specified).
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
This works if my named object of function has the exact same name as the prop to which I attempt to bind it. However, as I might have multiple instances of the Vue component and I might want to bind different data, I find using the same name for the variable less than ideal.
Now if I do as the Vue warning suggests, and name object / function the same as the prop, then the warning switches to that my data is not defined inside vue and to make sure it is reactive by reading: https://v2.vuejs.org/v2/guide/components-props.html
which, to be honest, doesnt really explain how to solve the issue,
or move the prop to the data level.
Which I can do (still gives the same warning), but kind of defeats the purpose of having props with my understanding of Vue.
This become more frustrating with anonymous vue instances.
e.g.
<script>
export default {
props: {
// records: {
// default: function(){return{}},
// type: Object
// }
},
data: function() {
return {
records: {} // define even an empty value in data for it to be 'reactive'
}
},
computed: {
fields: function() {
},
keys: function() {
return Object.keys(this.records)
}
},
methods: {
}
}
</script>
trying to use this as a component and set records to var myRecords = {"a": {}} fails:
<my-comp :records="myRecords"/>
So how exactly should I circumvent this? Where should I define my data then? and how should I handle the naming in the case of multiple instances?
A more fledged on example is found on a similar question:
Vue2: passing function as prop triggers warning that prop is already set
So I would like to define a function or object outside of vue, e.g. in an application, and pass this function or object to my vue instance.
It's hard to give a definitive answer because I don't know the specifics of how you have organized your code. Are you using Webpack? Single file components (.vue)? If yes to any of these, then you needn't use global variables in the way you have described in your question.
Your entire Vue app should consist of a single root Vue instance (which you instantiate with new Vue(...), and from there each component is rendered within the root component's template, and templates of those components, and so on.
Looking at the following template:
<my-comp :records="myRecords"/>
myRecords must be a property on the Vue component instance whose template contains the above. It could be declared within the data block, or as a computed property, or a prop, it doesn't matter.
Here's a small example:
<div id="app">
<my-comp :records="myRecords"></my-comp>
</div>
// Obtain records in some way and store it in a global variable
var records = ...
// This is the root Vue instance
new Vue({
el: '#app',
data: {
// You must store the records array in the Vue component like this
// for it to be referenced within the template.
// You can optionally transform the data first if you want.
myRecords: records.filter(r => r.name.startsWith('Bob'))
// ^ ^
// | |
// | +--- the global variable
// |
// +---- the name of the property on the component instance
}
})
Note that MyComp component does not access the records global variable in any way, it only takes its input through the records prop.

Component doesn't react to property change [array member accessed by index]

I have a method in the root Vue instance that updates some data in the "data" object of that same Vue instance (see below).
Here is my function in the methods object of the root Vue instance
updateRecipe(newRecipe) {
this.recipesArr[this.findRecipeIndex(newRecipe.id)] = newRecipe;
}
Here is my data object in the root Vue instance
data: {
recipesArr: JSON.parse(localStorage.getItem("storedRecipes")) // this is an array filled with objects
}
I then have a Vue component which has a "recipesarr" property bound to "recipesArr" in the root instance's data object.
Here is my Vue component called "recipe-list"
<recipe-list
v-bind:recipesarr="recipesArr">
</recipe-list>
Unfortunately, this Vue component doesn't react to any changes made by my "updateRecipe" function to "recipesArr". Why is this the case and how do I get it to react to changes made to "recipesArr" in the root Vue instance?
As you can see in the image below, the "recipesarr" property has updated; however, the Vue component does not update when I change "servingsNum" from 4 to 2.
You've all fixed the problem, but we might as well write it down. Vue cannot detect changes to array members when you access them directly by index. Use splice(), or Vue.set() to modify array members, and your changes will be detected. so...
updateRecipe(newRecipe) {
// Don't do this.
this.recipesArr[this.findRecipeIndex(newRecipe.id)] = newRecipe;
// Do this instead !
this.recipesArr.splice(this.findRecipeIndex(newRecipe.id),1,newRecipe);
}

Categories

Resources