Do properties always need to be declared in data: { } - vue.js

I'm getting familiar with Vue.js after having previously used AngularJS and I'd like to find if there's any way to get around explicitly declaring every data property in data { }.
In AngularJS, it was enough to declare ng-model as an HTML attribute and when the app initializes it adds them to the scope so they can be read/manipulated.
My concern is that I have a technical calculator I'm making with about 100 individual properties and declaring each property twice seems redundant.
Does Vue offer anyway to implicitly pick up the model properties without explicitly declaring them in the app data?
Example AngularJS without explicitly declaring model properties in the app:
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<div ng-app="exampleApp" ng-controller="exampleController">
<input ng-model="firstName" ng-change="makeName()">
<input ng-model="lastName" ng-change="makeName()">
<input ng-model="fullName">
</div>
<script>
angular.module("exampleApp", []).controller("exampleController", function ($scope) {
$scope.makeName = function () {
$scope.fullName = $scope.firstName + " " + $scope.lastName;
}
});
</script>
More or less the same app in Vue.js:
<script src="https://cdn.jsdelivr.net/npm/vue#2/dist/vue.js"></script>
<div id="exampleApp">
<input v-model="firstName" v-on:input="makeName">
<input v-model="lastName" v-on:input="makeName">
<input v-model="fullName">
</div>
<script>
var app = new Vue({
el: '#exampleApp',
data: {
firstName: "",
lastName: "",
fullName: ""
},
methods: {
makeName: function () {
this.fullName = this.firstName + " " + this.lastName;
}
}
});
</script>

Yes, for any 'variable' used in the HTML template, it must be declared in the JS.
While this can seem annoying when you have a lot of variables, it is important since the variable can be any one of a number of reactive things - simple variables, computed properties, methods etc. It wouldn't be possible to know what to create without also some syntax to specify what type of variable should be created, or to specify any default values.
In your example, fullName should probably be specified as a computed property - e.g.:
computed: {
fullName: function () {
return this.firstName + " " + this.lastName;
}
}
This way there is no need for the v-on:input - when the firstName and lastName variables update, this will trigger fullName to be recalculated.

Related

Vuejs2 - How to update the whole object from the server not losing reactivity?

I have a list of objects that can be updated from the database.
So, when I load the list, objects have only id and name.
When I click on an object I load other fields that can be of any length - that's why I don't load them with the objects in the list.
I found that when I update an object it can be difficult to keep reactivity https://v2.vuejs.org/v2/guide/reactivity.html so I need to find some workaround.
this code works almost okay:
axios.get('/api/news', item.id).then(function(response){
if (response){
Object.assign(item, response.data.item);
}
});
But the problem is the fields that have not been presented from the beginning is not 100% reactive anymore. What is going on is a new field has been updated reactively only when I change another, previous one. So, if I show 2 text field with old and new properties, if I change the second property, the field will not be updated until I change the first one.
I got item object from the component:
data () {
return {
items: [],
}
},
and
<div v-for="item in items" #click="selectItem(item)" >
<span>{{item.name}}</span>
</div>
Then item's been passed to the function selectItem.
What is the proper pattern to load new fields and keep them reactive? (NB: it's not the case when I can assign field by field - I want to reuse the same code no matter which object it is, so I need so the solution for updating an object at a time without listing all new fields.)
Note. This code works inside the component.
Completely revised post: Ok, the example you give uses an array, which has its own caveats, in particular that you can't directly set values like vm.items[indexOfItem] = newValue and have it react.
So you have to use Vue.set with the array as the first argument and the index as the second. Here's an example that adds a name property to object items and then uses Vue.set to set the item to a new object created by Object.assign.
new Vue({
el: '#app',
data: {
items: [{
id: 1,
other: 'data'
}, {
id: 2,
other: 'thingy'
}]
},
methods: {
selectItem(parent, key) {
const newObj = Object.assign({}, parent[key], {
name: 'some new name'
});
Vue.set(parent, key, newObj);
setTimeout(() => {parent[key].name = 'Look, reactive!';}, 1500);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item, index in items" #click="selectItem(items, index)">
<span>{{item.name || 'empty'}}</span>
</div>
<pre>
{{JSON.stringify(items, null, 2)}}
</pre>
</div>
Have a look at Change-Detection-Caveats Vue cannot detect property addition or deletion if you use "normal assign" methods.
You must use Vue.set(object, key, value)
Try something like the following:
axios.get('/api/news', item.id).then(function(response){
if (response){
let item = {}
Vue.set(item, 'data', response.data.item)
}
});
Than item.data would than be reactiv.
Can simply use Vue.set to update this.item reactively
axios.get('/api/news', item.id).then(function(response){
if (response){
this.$set(this, "item", response.data.item);
}
});

What is the mechanism of calling a function from a template in Vue js

I am trying to learn Vue.js. I am following a tutorial on this site https://scrimba.com/p/pZ45Hz/c7anmTk. From here I am not getting something clear.
Here is the code below and my confusion as well :
<div id="app">
<wizard :name="harry" :cast="oculus_reparo" ></wizard>
<wizard :name="ron" :cast="wingardium_leviosa"></wizard>
<wizard :name="hermione" :cast="alohomora" ></wizard>
</div>
// emojify returns the corresponding emoji image
function emojify(name) {
var out = `<img src="emojis/` + name + `.png">`
return out
}
// cast returns a spell (function) that decorates the wizard
function cast(emoji) {
var magic = emojify("magic")
return function (wizard) {
return wizard + " " + magic + " " + emoji + " " + magic
}
}
Vue.component("wizard", {
props: ["name", "cast"],
template: `<p v-html="cast(name)"></p>`
})
var app = new Vue({
el: "#app",
data: {
harry : emojify("harry" ),
ron : emojify("ron" ),
hermione : emojify("hermione")
},
methods: {
// oculus_reparo returns a spell (function) that repairs glasses
oculus_reparo: cast(emojify("oculus-reparo")),
// wingardium_leviosa returns a spell (function) that levitates an object
wingardium_leviosa: cast(emojify("wingardium-leviosa")),
// alohomora returns a spell (function) that unlocks a door
alohomora: cast(emojify("alohomora"))
}
})
So far what I have got is that, I have created a component named wizard which takes two properties - name and cast. name is getting the value from data, and so far I understand that cast is calling the method with a parameter.
So both of them should return their specific image. My first confusion: Where does wizard come from and how is it showing the data.name image? If it is because of the method call in the template then why does emoji return another image?
I think the example is unnecessarily complex for the ideas you're looking to learn.
wizard is being globally registered with Vue by Vue.component("wizard", ...). When Vue interprets each wizard call in the template it will replace it with <p v-html="cast(name)"></p> which is set in the wizard component definition. Here name gets mapped to the property that is set via :name=. v-html is just saying to render as html the return value of cast(name), here cast is the function property that is passed to the component and not the cast function locally defined. Everything after that happens as you would expect where emojify returns a template literal that is passed to cast, that then returns a function, which combines the emoji and other properties.

Set a variable inside a v-for loop on Vue JS

I have a v-for loop with vue.js on a SPA and I wonder if it's posible to set a variable at the beginning and then just print it everytime you need it, because right now i'm calling a method everytime i need to print the variable.
This is the JSON data.
{
"likes": ["famiglia", "ridere", "caffè", "cioccolato", "tres leches", "ballare", "cinema"],
"dislikes":["tristezze", "abuso su animali", "ingiustizie", "bugie"]
}
Then I use it in a loop:
<template>
<div class="c-interests__item" v-for="(value, key) in interests" :key="key" :data-key="key" :data-is="getEmotion(key)" >
// NOTE: I need to use the variable like this in different places, and I find myself calling getEmotion(key) everythime, is this the way to go on Vue? or there is another way to set a var and just call it where we need it?
<div :class="['c-card__frontTopBox', 'c-card__frontTopBox--' + getEmotion(key)]" ...
<svgicon :icon="getEmotion(key) ...
</div>
</template>
<script>
import interests from '../assets/json/interests.json'
... More imports
let emotion = ''
export default {
name: 'CInfographicsInterests',
components: {
JSubtitle, svgicon
},
data () {
return {
interests,
emotion
}
},
methods: {
getEmotion (key) {
let emotion = (key === 0) ? 'happy' : 'sad'
return emotion
}
}
}
</script>
// Not relevanty to the question
<style lang='scss'>
.c-interests{...}
</style>
I tried adding a prop like :testy="getEmotion(key)" and then { testy } with no luck...
I tried printing { emotion } directly and it doesn't work
So, there is anyway to acomplish this or should i stick calling the method every time?
Thanks in advance for any help.
It's not a good idea to use methods inside a template for non-user-directed actions (like onClicks). It's especially bad, when it comes to performance, inside loops.
Instead of using a method, you can use a computed variable to store the state like so
computed: {
emotions() {
return this.interests.map((index, key) => key === 0 ? 'happy' : 'sad');
}
}
This will create an array that will return the data you need, so you can use
<div class="c-interests__item"
v-for="(value, key) in interests"
:key="key" />`
which will reduce the amount of times the item gets re-drawn.

vue.js method can't access variable from data object with multiple rows

I am currently learning vue.js and having trouble accessing data in the methods.
data is loaded and set as a global variable (for now, this will probably change but not part of the problem now i think)
through ajax call this data is received:
data":[{"itemId":"58646f066803fa62388b4573","color":"#ffb878","name":"test1","startDate":"04/24/2017","work":"9.25"},{"itemId":"58646f066803fa62388b4572","color":"#ffb878","name":"test2","startDate":"04/24/2017","work":"4.25"},{"itemId":"58646f066803fa62388b4571","color":"#a4bdfc","name":"test3","startDate":"05/01/2017","work":"24.00"}]
which is set as a global (variable data is set outside of the functions) with:
...success: function (jsonObj)
{
data['item'] = jsonObj.data
....
now for the vue part:
var app = new Vue({
el:'#canvas',
data: {
items: data['item']
},
methods: {
moveItem: function(){
console.log("new date: "+this.startDate);
}
}
})
the html:
<div v-for="row in items" class="entirerow" v-bind:id="'row'+row.itemId">
<div class="itemrow">{{ row.name }}</div>
<div class="itemrow"><input type="text" v-model="row.startDate" #change="moveItem"></div>
<div class="itemrowlast">{{ row.work }}</div>
</div>
this nicely shows 3 rows with the correct data in each row. So far so good. But now if I change something in the input value the method moveItem is triggered but states "new date: undefined" in the console.log
I've tried console.log("new date: "+this.items.startDate) as well but no cigar and then it would seem the method wouldn't know which row is handled.
How can I access the correct data in the method, so from a certain row in the loop?
Thanks!
You refer to data object in method (this.startDate) not to item
moveItem: function(){
console.log("new date: "+this.startDate);
}
You can change your code like this
template
#change="moveItem(row)"
script
moveItem: function(row){
console.log("new date: " + row.startDate);
}

access object with dynamic variable vue.js

This is my object
var users ={
twitter : {
name : //,
lastname : //
},
facebook : {
name : //,
lastname : //
}
}
}
I have a dynamic variable activeuser that updates from Facebook to twitter.
What i'm trying to do is refer to the inner object in users depending on the value of activeuser. I need to give my div something like this class :
<div class=' {{users.activeuser}}'></div>
I know this is not how it should be done with vue.js. Do you have any suggestions?
Thank You!
Using VueJS you should be able to assign your dynamic variable to a Vue Model when you load the new object using a Vue setter $set('property name', 'value')
Example AJAX retreival:
$.getJSON('myURL.html?query=xxx', function(data, textStatus, jqXHR){
try{
MyVue.$set('dynamicObject', data);
}
catch(e){}
});
A generic Vue may look like this:
var MyVue = new Vue({
el:'#exampleDiv',
data: {
dynamicObject : ''
}
});
Bound to an example HTML element:
<div id="exampleDiv">
<label class="{{dynamicObject.activeuser}}">{{dynamicObject.username}}</label>
</div>
In the case that you have an object with an array of objects which also contain properties Vue makes it very simple to create many HTML elements (for each child object) by simply adding a v-repeat (example) to the desired HTML and assigning the datasource:
<div id="exampleDiv">
<label v-repeat="dynamicObject" class="{{dynamicObject.activeuser}}"></label>
</div>