Vuejs problem ; How to bind new elements added to DOM? - vue.js

I Want to add new element to the DOM after new Vue instance created.
I know I can re initiate the instance of vue but it seems it is not good idea because vue have to re render all elements again. So is there a method to bind just new element to the data scope?
<body>
<div id="app">
<span>{{ a }}<span>
<div id="newElem"></div>
</div>
</body>
<script>
$(document).ready(function () {
var mainApp = new Vue({
el: "#app",
data: {
"a": "aa",
"b": "bb"
}
});
$("#newElem").html("<span>{{ b }}</span>")
});
</script>
This code renders {{ b }} unbinded. I am looking for a method for preventing this.
Thanks

Have you tried something like this?
<div v-html="newElem"></div>
el: "#app",
data: {
"a": "aa",
"b": "bb"
},
created() {
this.newElem = "<span>{{ b }}</span>";
}

It's really hard to tell your ultimate goal here but I'll take a shot.
Ideally, you wouldn't touch the DOM, you'd want to update the data and let Vue handle the DOM for you.
I think you want to initialize b as undefined and set that when you're ready for it to be displayed.
data: {
a: 'aa',
b: undefined,
}
Then in your template you can have something like this:
<span v-if="b">{{ b }}</span>
This way the span won't display until b is truthy.
Then, later, when b is ready, you would mainApp.b = 'bb'.
Here's a quick demo: http://jsfiddle.net/crswll/vtu8nzea/1/
If you make add a more complete use case I can likely help further.
Also, this answer might help as well. It's kind of like the next level of this thinking: Vuejs: v-model array in multiple input

Related

When should a 'watcher' be used over an 'event'?

I'm under the impression that watchers should be used as a last resort in Vue. I know that computed properties are favoured over watchers. However, there are times when you can use a watcher or an #change to do the same thing. I feel like an event is a better and more explicit way of performing an operation in the following case:
From VueJS Guide - Computed Properties and Watchers
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
watch: {
// whenever question changes, this function will run
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
_.debounce(this.getAnswer, 500)
}
},
...
}
or you could do this with an #change or #input event like this:
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" #change="getAnswer">
</p>
<p>{{ answer }}</p>
</div>
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'I cannot give you an answer until you ask a question!'
},
methods: {
// whenever question changes, this function will run
getAnswer: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
_.debounce(this.getAnswer, 500)
}
},
...
}
I'm not sure why a watcher would be used here over an #change or similar event. I feel that it's better to use an event in this case because it is more explicit.
So my questions are:
Am I performing the operation inefficiently in the second example?
What are the drawbacks to using an event if there are any?
What are the drawbacks to using a watcher here if there are any?
The guide I'm referencing in the first example states watchers are "useful when you want to perform asynchronous or expensive operations in response to changing data", however can one not just use an event and an async method to perform the asynchronous operations?

Vue: How to perform reactive object change detection in v-for?

I read this documentation but cannot use the proposed solution.
I have a v-for loop over objects. These objects are changed dynamically over time and I need that change to show reactively in the v-for loop.
<b-row lg="12" v-for="data in objects" :key="data.id">
<div v-if="data.loading">
loading...
{{data.loading}}
</div>
<div v-else>
loaded, done
{{data.loading}}
</div>
</b-row>
In my methods, I have a for loop that downloads data for each object and changes the object value like this:
for(var i = 0; i<response.ids.length; i++){
var newId = response.ids[i].id
this.objects.newId = {"loading":true, "id": newId}
downloadSomething(newId).then(res => {
this.objects.newId = res[0] //<-- this change needs to be shown reactively.
})
}
According to Vue documentation, Object changes are not reactive:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue propses some workaround like this:
Vue.set(vm.userProfile, 'age', 27)
UPDATE
But for my case, this just creates a new parameter in the object with the same ID and creates a duplicate key warning and other problems.
I also tried Vue.delete just before Vue.set but it is not actually deleting.
Is there a way to not replace the key/value pair but add more/change parameters to the first child of the root with the ID of newID
Thanks!
Solution: Replace this.objects.newId = res[0] with this.$set(this.objects, 'newId', res[0]) and it should work.
Explanation: this.$set is just an alias to Vue.set, available within any Vue instance. Bear in mind that vm (stands for view model) in the example is the same as this and equals to Vue component instance. Also due to ES5 JS restrictions, you have to set objects' properties explicitly via Vue.set/this.$set to manually trigger re-render. This problem will be resolved when Vue 3.0 is released.
Hope this helps, if you need any clarifications - feel free to ask.
Try that:
downloadSomething(newId).then(res => {
this.$set(this.objects, 'newId', res[0])
})
you need Vue.set() when you want to define a new property to an existing object (not directly to the data), and this function will assign the new property to the built-in watcher, and it will become reactive as well.
its all being explained in the docs: https://v2.vuejs.org/v2/guide/reactivity.html
in your case, it seems to be all you need to make it work. in my example:https://jsfiddle.net/efrat19/eywraw8t/484131/ an async function fetches the data and define a new property to contain te response.
new Vue({
el: "#app",
data: {
objects:{
property1:12
}
},
methods: {
fetchDataAndAddProperty(response){
fetch('https://free.currencyconverterapi.com/api/v6/countries').then(res =>
res.json()).then(data => Vue.set(this.objects,'property2',data))
}
}
})
and the template:
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
<div lg="12" v-for="(val,key,index) in objects" :key="index">
{{key}}:{{val}}
</div>
<button #click="fetchDataAndAddProperty()">fetch</button>
</div>
as you can see in the fiddle, the new property becomes reactive and being displayed as well.

Does Vue have bilateral bind?

<p contenteditable="true" v-bind="message"></p>
Object #message does not change when I edit the HTML element. Is there any simple way to do this using Vue.js?
Unfortunately, contenteditable doesn't work with Vue bindings such as v-model, it's recommended that you use a library such as medium.js to build your own component.
However, if you're just trying to do something simple and keep the data in sync you can probably do that yourself:
View Model
new Vue({
el: '#app',
methods: {
updateMessage() {
this.message = this.$refs.message.innerText;
}
},
watch: {
message() {
this.$refs.message.innerText = this.message;
}
},
data: {
message: 'Hello'
}
})
HTML
<p ref="message" contenteditable="true" #keyup="updateMessage" class="editable">{{message}}</p>
 
As you can see you have to deal with updating the DOM yourself when using contenteditable. What I'm actually doing here is using a ref to target the contenteditable, so I can get the inner text via this.$refs.message.innerText. I've then added a #keyup event which calls the updateMessage method to update the message data property. I've then added a watcher which reverses this process, so when message is updated it updates the contenteditable.
Here's the JSFiddle: https://jsfiddle.net/3ngc9486/
Yes it has a two-way binding directive v-model, but it works only with input elements. So, instead of using a p element and handling that with complex JS, use a textarea with v-model and it will work out of the box.
<textarea v-model="message"></textarea>
here is an example.

Rendering a lot of components

I am trying to make a small application with VueJS and I am having some performance issues.
I'm not sure that this is something I can do something for, but asking anyway.
My goal is to render a lot of components (more than 10,000 at the same moment). But the time it takes to Vue to render thoses components and update the dom is quite long.
Here I made a small JsFiddle so you can try on your side: https://jsfiddle.net/rt5cjbby/
At home, with a badass i7 processor, it takes 400ms to render. Which is, much more when you test with more components.
My question is: Is there any technique I could use to have better performances?
Is it possible to update the DOM every 100 components to be rendered? The client could see the first 100 components very quickly then the browser would continue to render....
Here is my example code if you don't want to open jsFiddle:
some_table = []
for (let i = 0; i < 2000; i++) {
some_table.push({
foo: 'bar'
})
}
let someComponent = Vue.extend({
template: '#some-component',
props: ['input']
})
new Vue({
el: '#app',
data: {
lines: some_table
},
components: {
someComponent
}
})
And my templates:
<div id="app">
<div v-for="line in lines">
<some-component :input="line"></some-component>
</div>
</div>
<template id="some-component">
<div>
{{ input.foo }}
</div>
</template>
Of course, I could fill my table every 0.01s but I don't feel that this is the best solution.
PS: I accept responses like "[Angular|React] are better for your usecase because ...."
Thank you very much for your help/experiences/advices and have a nice day
If you are interested in only rendering the component's end result, you can easily achieve exceptional performance utilizing the render function.
Here is a complete working Fiddle of the following:
Template:
<div id="app"></div>
Javascript:
some_table = []
for (let i = 0; i < 10000; i++) {
some_table.push({
foo: 'bar'
})
}
new Vue({
el: '#app',
data: {
lines: some_table
},
render: function (createElement) {
if (this.lines.length) {
return createElement('div', this.lines.map(function (line) {
return createElement('div', line.foo)
}))
} else {
return createElement('p', 'No items found.')
}
}
})
setTimeout(() => {
for (let i = 0; i < 10; i++) {
some_table.unshift({
foo: 'bar stool'
})
}
}, 2500)
In this example, 10,000 "cells" are rendered almost immediately (about 450ms while profiling on my machine). After a 2.5 second delay, an additional 10 new records are added as well, since the render function will respond to changes to the array. This allows you to make changes to the rendered state as needed by modifying the source array.
Note that you can still perform complex 2-way data binding via your own v-model implementations in render functions, although it is significantly harder to maintain.

I am really confused with the radio input in Vue.js

From the Vue guide, I saw these things:"For radio, checkbox and select options, the v-model binding values are usually static strings (or booleans for checkbox):" and
<!-- `picked` is a string "a" when checked -->
<input type="radio" v-model="picked" value="a">
But when I try to render picked in the tag , it does not work. why?
Here's my code.(in this case, it's one)
When I click the radio, it does not reflect in the tag span.
Your missing data, try:
new Vue({
el:'#app',
data: {
picked: 'One'
}
})
Example: http://codepen.io/anon/pen/NpPmgp
You have to add data as well in the vue instance:
new Vue({
el:'#app',
data: {
picked: 'Two'
}
})
Check this:
http://codepen.io/anon/pen/XMJQRB