Improving performance of Vue.js application - vue.js

In our application, there is large set of data (json ~ 630KB) getting populated which consumes around 219.28 MB of JavaScript memory displayed in chrome task manager.
It loads the data from the api and uses vuex to store the data and generates the menu recursively.
Since it is a big application, I mimicked the same behaviour with a sample vue.js application by loading 100000 records to understand how the memory utilization works.
Each time "Run test" is clicked, 200 MB memory is consumed in the task manager and is not released until so much time even if the page is refreshed.
I tried to use "beforeDestroy" hook to clear the object from vuex but it is not working. I would like to know if the data itself is causing the slow performance or any other improvements can be done.
<html>
<head>
</head>
<body>
<div id="myApp">
<button #click="setCounterModule()">Counter</button>
<button #click="setCustomersModule">Customers</button>
<br />
<br />
<div>
<button-counter v-if="currentModule=='counter'"></button-counter>
<customers v-else></customers>
</div>
</div>
<script src="vue.js"></script>
<script src="vuex.js"></script>
<script>
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
});
Vue.component('customers', {
computed: {
customers() {
return store.state.customers
}
},
template: '<div><button #click="testMe" :disabled="loading">Run Test</button><div v-for="c in customers">{{ c.name }}</div></div>',
data: function() {
return {
loading: false
}
},
methods: {
testMe: function () {
this.loading = true;
let customers = [];
for (var i=0; i < 100000; i++) {
let customer = {'name': 'John' +i, 'address': 'Addr' + i};
customers.push(customer);
}
store.commit('assignCustomers', customers);
this.loading = false;
}
},
beforeDestroy() {
// console.log('vuex destroyed');
// store.commit('assignCustomers', null);
}
});
const store = new Vuex.Store({
state: {
customers: null
},
mutations: {
assignCustomers (state, customers) {
state.customers = customers;
}
}
});
var app = new Vue( {
el: "#myApp",
data: function() {
return {
currentModule: "counter"
}
},
methods: {
setCounterModule: function() {
this.currentModule = "counter";
},
setCustomersModule: function() {
this.currentModule = "customers";
}
}
});
</script>
</body>
</html>

Related

How to handle infinite GET Image requests when dealing with broken image url in Vue 2?

I'm developing a new version of my website in Vue 2 and I have created a component to handle image broken links, swapping the url domain, in order to display images stored in my local host and also those stored in the production server. The problem is that sometimes the image filename in the server is different than what is set on my local host database (because the user changed the image on the production live version), and then I get infinite requests looping in the console since neither image url are valid.
I'm also using Bootstrap-Vue to lazy-load the images.
This is my code:
<template>
<b-img-lazy :src="'https://example.com/uploads/' + photo" #error.native="replaceSrc" :alt="titulo" :title="title" class="thumb"></b-img-lazy>
</template>
<script>
export default {
name: "ImgCarousel",
props: {
photo: {
type: String
},
title: {
type: String
}
},
data: function () {
return {
index: null
}
},
methods: {
replaceSrc(e) {
e.target.src = 'http://localhost:8888/uploads/' + this.photo;
},
}
}
</script>
How do I stop the GET requests from entering a infinite loop?
I also reached the solution below. Probably not elegant as Nikola's answer, although it needs less coding. Basically it adds a counter on each call for "replaceSrc" to prevent looping on the broken links:
<template>
<b-img-lazy :src="'https://example.com/uploads/' + photo" #error.native="replaceSrc" :alt="title" :title="title" class="thumb"></b-img-lazy>
</template>
<script>
export default {
name: "ImgCarousel",
props: {
photo: {
type: String
},
title: {
type: String
}
},
data: function () {
return {
counter: 0
}
},
methods: {
replaceSrc(e) {
if (this.counter == 0) {
e.target.src = 'http://localhost:8888/uploads/' + this.photo;
this.counter ++;
} else if (this.counter == 1) {
e.target.src = 'https://via.placeholder.com/300x190';
this.counter ++;
}
},
}
}
</script>
BTW the "index: null" data on my question was a wrong code I forgot to remove.
Try to check if image exists:
new Vue({
el: '#demo',
data: function () {
return {
startImg: 'https://picsum.photos/150',
index: null,
photo: '100',
title: '',
titulo: '',
url: 'https://picsum.photos/'
}
},
methods: {
replaceSrc(e) {
this.checkIfImageExists(this.url + this.photo, (exists) => {
exists ?
e.target.src = this.url + this.photo :
this.titulo = 'no image'
})
},
checkIfImageExists(url, callback) {
const img = new Image();
img.src = url;
if (img.complete) {
callback(true);
} else {
img.onload = () => callback(true)
img.onerror = () => callback(false)
}
},
change() {
this.startImg = this.startImg ? '' : 'https://picsum.photos/150'
},
breakSource() {
this.startImg = "wrong url"
this.photo = "wrong url"
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="demo">
<div>
<b-img-lazy :src="startImg" #error.native="replaceSrc" :title="title" :alt="titulo" class="thumb"></b-img-lazy>
</div>
<button #click="change">Change source</button>
<button #click="breakSource">Break source</button>
</div>

Vue2 create component based on data

I want to create a component based on ajax api response or data which include:
template
data
methods - there may be several methods
Remark: response or data is dynamic and it is not saved in file.
I have tried to generate and return result like :
<script>
Vue.component('test-component14', {
template: '<div><input type="button" v-on:click="changeName" value="Click me 14" /><h1>{{msg}}</h1></div>',
data: function () {
return {
msg: "Test Componet 14 "
}
},
methods: {
changeName: function () {
this.msg = "mouse clicked 14";
},
}
});
</script>
and do compile above code :
axios.get("/api/GetResult")
.then(response => {
comp1 = response.data;
const compiled = Vue.compile(comp1);
Vue.component('result-component', compiled);
})
.catch(error => console.log(error))
I got error on Vue.compile(comp1) -
Templates should only be responsible for mapping the state to the UI. Avoid placing tags with side-effects in your templates, such as
<script>, as they will not be parsed.
Thanks in advance
Your Api should return a JSON with every property required by a Vue component (name, data, template, methods), note that methods needs to be converted into an actual js function (check docs about that)
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
data() {
return {
apiComponent: { template: '<div>Loading!</div>' }
};
},
methods: {
loadApiComponent() {
setTimeout(() => {
this.buildApiComponent(JSON.parse('{"name":"test-component14","template":"<div><input type=\\\"button\\\" v-on:click=\\\"changeName\\\" value=\\\"Click me 14\\\" /><h1>{{msg}}</h1></div>","data":{"msg":"Test Componet 14 "},"methods":[{"name":"changeName","body":"{this.msg = \\\"mouse clicked 14\\\";}"}]}'));
}, 2000);
},
buildApiComponent(compObject) {
const {
name,
template,
data,
methods
} = compObject;
const compiledTemplate = Vue.compile(template);
this.apiComponent = {
...compiledTemplate,
name,
data() {
return { ...data
}
},
methods: methods.reduce((c, n) => {
c[n.name] = new Function(n.body);
return c;
}, {})
};
}
},
mounted() {
this.loadApiComponent();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<component :is="apiComponent" />
</div>

listen to events from dynamic vue components

How would you listen to an event emitted by a dynamically created component instance?
In the example, the top component is added in the DOM, while the second is dynamically created in javascript.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
}
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
button_counter_instance.$on('myevent', function(count) {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
If I've understood what you want then you just need to listen for the event on the inner component and pass it on.
I've used arrow functions in a couple of places to avoid problems with this bindings. Otherwise I've tried to leave your code unchanged as much as possible. Changes marked with ****.
Vue.component("button-counter", {
data: function() {
return {
count: this.initial_count
}
},
props: ['initial_count'],
methods: {
add: function() {
this.count++
this.$emit('myevent', this.count)
}
},
template: '<button v-on:click="add">You clicked me {{ count }} times.</button>'
})
let app = new Vue({
el: "#app",
data() {
return {
initial_count: 10,
}
},
mounted: function() {
let initial_count = this.initial_count
let ButtonCounterComponentClass = Vue.extend({
data: function() {
return {}
},
render(h) {
return h("button-counter", {
props: {
initial_count: initial_count
},
// **** Added this ****
on: {
myevent: count => {
this.$emit('myevent', count);
}
}
// ****
})
}
})
let button_counter_instance = new ButtonCounterComponentClass()
button_counter_instance.$mount()
// **** Changed the next line ****
button_counter_instance.$on('myevent', count => {
console.log('listened!')
this.say(count)
})
this.$refs.container.appendChild(button_counter_instance.$el)
},
methods: {
say: function(message) {
alert(message)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<button-counter initial_count=20 v-on:myevent="say"></button-counter>
<div ref='container'></div>
</div>
It's important to understand that button_counter_instance is not an instance of your button-counter component. You've wrapped it in another component, albeit a component that doesn't add any extra DOM nodes. So listening on the wrapper component is not the same as listening on button-counter.
Docs for what you can pass to h: https://v2.vuejs.org/v2/guide/render-function.html#The-Data-Object-In-Depth

Vue. Autosave input value with localstorage

I have a component with a form for which I need to implement data autosave when entering content. How to apply an example for v-model = "titleName" to v-model = "article.title[currentLanguage]"?
P.S. currentLanguage is taken from store
<div id="app">
Title is <input type="text" name="titleName" id="titleName" v-model="titleName">
Title is <input type="text" name="article" id="article"
v-model="article.title.en">
</div>
<script>
var app = new Vue({
el:'#app',
data: {
titleName: '',
article: {
title: {
en: null,
ru: null
}
}
},
computed: {
availableLanguages() {
return this.$store.state.languages;
}
},
created() {
this.setDefaults();
},
mounted () {
const SAVED = localStorage.getItem('content')
if (SAVED) {
this.titleName = SAVED
}
},
watch: {
titleName(newTitleName) {
localStorage.content = newTitleName;
}
}
</script>
https://codepen.io/pershay/pen/EJQGGO
Just use localStorage.setItem('titleName', newTitleName) and localStorage.getItem('titleName')
You don't need to load on mounted. Place it in the data function itself. I think this is the better way:
var app = new Vue({
data:() => ({
titleName: localStorage.getItem('titleName'),
article: JSON.parse(localStorage.getItem('article'))
}),
watch: {
titleName(newTitleName) {
localStorage.setItem('titleName', newTitleName)
},
article(newArticle) {
handler: function(newArticle) {
localStorage.setItem('article', JSON.stringify(newArticle))
},
deep: true
},
}
The example above also includes a deep object watch.

VueJS Simple Alert Appear

Can someone help me make this simple alert work? What I wanted was if I click the trigger button, the text would appear but then the component should do its part.
I'm new and learning VueJS and now is in the components part but I haven't fully grasped it yet.
Here's the link: JSBIN
Snippet of the JS Script
Vue.component('alert', {
template: '#alert',
props: {
errors:false
},
data: function() {
return {
message:""
}
},
methods: {
appear: function (status) {
if(status=="yes") {
errors = true;
message = "Appeared";
}
}
}
});
var myapp = new Vue({
components: 'alert',
el: '#app',
data: {
},
methods: {
trigger: function() {
this.$alert.appear("yes");
}
}
});
To make your code work the way I think you are trying to get it to work I made a few changes.
Template
<div id=app>
<button #click="trigger">Trigger</button>
<alert ref="alert"></alert>
</div>
Code
Vue.component('alert', {
template: '#alert',
data: function() {
return {
message:"",
errors: false
}
},
methods: {
appear: function (status) {
if(status=="yes") {
this.errors = true;
this.message = "Appeared";
}
}
}
});
var myapp = new Vue({
components: 'alert',
el: '#app',
data: {
},
methods: {
trigger: function() {
this.$refs.alert.appear("yes");
}
}
})
Here is the updated bin.
This is a pretty atypical way to do this kind of thing though. Here is an example of a more idiomatic alert.
Vue.component('alert', {
props:["message","show"],
template: '#alert',
});
var myapp = new Vue({
el: '#app',
data: {
errors: false,
errorMessage: null
},
methods: {
trigger: function() {
this.errors = true
this.errorMessage = "Whoops!"
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id=app>
<button #click="trigger">Trigger</button>
<alert :message="errorMessage" :show="errors"></alert>
</div>
<template id="alert">
<div v-show="show">
<div>{{ message }}</div>
</div>
</template>
In this second example, the information to show in the alert is passed down to the alert component via properties and the alert is also triggered via a property. This is how you would typically do it in Vue.