Optimization is a crucial part of the design process when building internet applications. According to studies, the ideal time for a website to load a page is 2-3 seconds, any longer than that. The user's chance to leave the page before it loads increases dramatically, and pages that load faster see increased ad revenue from their userbase.
This is even more crucial when visiting pages on a mobile device, which see an even higher bounce rate than pages loaded on a desktop as the time to load increases. With the increase in users visiting sites on mobile devices, Google has since switched to favoring mobile-first indexing. This means that optimization is even more crucial, with Google favoring indexing websites based on Googlebot's smartphone agent moving forward.
Today's larger applications deal with large data sets and bundles and perform complex calculations. Thankfully, Vue and Wijmo's Vue DataGrid, FlexGrid, give you options on how to optimize your Vue DataGrid application.
Here are the five steps for optimizing and creating a Vue datagrid application:
- Unsubscribing From Observables
- Keep Bundle Sizes Small by Code-Splitting With Routes
- Lazy Load Modules
- Reducing DOM Elements With FlexGrid Virtualization
- Prevent Elements From Re-rendering
Using FlexGrid with Vue is so easy that it's fun!
Unsubscribing From Observables
Observables do exactly what they say; they're things you wish to observe and take action on. They offer an easy way for developers to pass data both to and around their applications. In Vue, they're used for event handling, asynchronous programming, and managing different sets of data. However, improper observables can lead to decreased performance and severe memory management issues.
When using observables, they use a subscribe method to get the data and watch to see any changes, all done asynchronously. Typically, you subscribe to a method within a service that makes an HTTP call to get your data, and in most cases, this is done within the created() method of your component:
<template>
...
</template>
<script>
import { mockService } from '@/services';
export default {
name: 'mock-component',
data() {
return {
apiData: []
};
},
created() {
this.mockSubscription = mockService.getAPIData().subscribe(data => {
this.apiData = data;
});
}
};
</script>
With that complete, we can now asynchronously access our data from the API and receive any changes made to the data through our subscription. However, we're missing one crucial detail: the unsubscribe() method. The subscription connection will remain open with our component set up if we navigate away from it. This can lead to memory leaks, causing severe performance issues.
Since we want this connection to remain open while the component is loaded, we'll wait until the component gets removed from the DOM to unsubscribe. Vue gives us a lifecycle hook that will allow us to do this, called beforeDestroy():
<template>
...
</template>
<script>
import { mockService } from '@/services';
export default {
name: 'mock-component',
data() {
return {
apiData: []
};
},
created() {
this.mockSubscription = mockService.getAPIData().subscribe(data => {
this.apiData = data;
});
},
beforeDestroy() {
this.mockSubscription.unsubscribe();
}
};
</script>
When our users navigate away from this component, our application will unsubscribe from this observable, closing the connection and preventing memory leaks. If you'd like to learn more about observables, RxJS has documentation to provide you with more information.
Keep Bundle Sizes Small by Code-Splitting With Routes
When building websites, you'll have multiple pages for your site in most circumstances. Our app will have a Home and an About page for our example. Even with just two pages, we'll need to implement a router within our application:
import HomePage from './HomePage.vue';
import AboutPage from './AboutPage.vue';
const routes = [
{ path: '/', component: HomePage },
{ path: '/about', component: AboutPage }
];
We use this default method when implementing a router in Vue. These components, HomePage, and AboutPage, will be downloaded even if the user were to visit another page, such as a Contact page. When we build our application, both the HomePage and AboutPage components get put together in the same bundle. For small applications like this, where we have 2 or 3 pages, this won't be an issue, but as our applications get larger and the bundle sizes increase, this can cause longer loading times when users try to load our application.
To avoid bundling together components that won't be loaded at the same time, we can split up our code into separate bundles for each of our routes by using dynamic imports:
const routes = [
{ path: '/', component: () => import('./HomePage.vue') },
{ path: '/about', component: () => import('./AboutPage.vue') }
];
Using the import() method to import our components, we'll decrease our bundle size by splitting these components up into separate bundles when we build our application. This will improve load times for our application by only loading the bundles that have the code that we need based on the page that the user navigates to.
Lazy Load Modules
In the previous section, we discussed using code-splitting with routing to reduce your bundle sizes by breaking your main bundle file into smaller ones. The cool thing is that we aren't limited to splitting apart our bundle file just with routing; we can also break apart our bundle files by lazy-loading our modules the same way as we did with routing:
<template>
...
</template>
<script>
export default {
name: 'mock-component',
data() {
...
},
components: {
UserComponent: () => import('./UserComponent.vue')
}
}
</script>
Our UserComponent component will be placed inside a separate bundle when our application loads. That bundle will be loaded when our MockComponent is loaded instead of when the application and main bundle file are loaded.
There is one drawback to this, however: if our users have a slow connection, or if the bundle file that we're loading in is large, not all elements of the page will load at the same time; in our example, users may see a delay between when the page loads and when the UserComponent gets loaded into the page. Because of this, you don't want to lazy load all of your components; you'll need to develop knowledge of which modules you won't need immediately and lazy load those while keeping the rest within the main bundle file.
Reduce DOM Elements With FlexGrid Virtualization
The primary purpose of Wjimo's Vue DataGrid, FlexGrid, is to convert JavaScript objects into DOM elements that the user can interact with. In many instances, this data consists of hundreds, thousands, or even millions of rows of data; creating DOM elements for each of these items can be highly resource-heavy, causing slow and bloated pages.
Virtualization is the process of keeping track of which portions of the data are visible to the user and only rendering those sections in the DOM. This dramatically reduces the number of DOM elements in the document tree and improves performance, especially when working with very large data sets.
Wijmo exposes the visible part of the data through its viewRange property; whenever the user resizes the screen or scrolls the grid, the viewRange gets updated, which updates the DOM. To prevent the number of elements in the DOM from ballooning, FlexGrid takes the cells that scroll out of the viewRange and recycles them, removing from the data that they were storing and repopulating them with the new data that is coming into the viewRange. This keeps your DOM lean and your application fast and lightweight.
We can see this demonstrated in this sample:
As you can see, there are currently 100 rows of data in the grid and only 60 cell elements being rendered by the DOM. We get this number by using the following code:
flexgrid.updatedView.addHandler((s, e) => {
this.rowCount = s.rows.length.toString();
this.cellCount = s.hostElement.querySelectorAll(".wj-cell").length.toString();
});
The s.hostElement.querySelectorAll('.wj-cell') method returns the an array of elements rendered in the DOM that have the .wj-cell class appended to them. As we scroll down the grid, we see that the number of rows of data within FlexGrid increase, but the number of cell elements stay the same:
Prevent Elements From Rerendering
When elements of our pages change, our application will go through and rerender our elements in the DOM. When we have pages with components that will experience regular changes, we want our application to perform rerendering. However, there are times where we know that some aspects of our page will never change after they are initially rendered. Because we know they won't change, we don't want our application to rerender them because this will decrease the performance of our application. Luckily, Vue gives us a directive to tell our application only to render the element once: v-once.
By adding the v-once directive to an element within our component, when the application goes through and performs rerendering, it will skip over the element marked by v-once, which will help us optimize our application's performance:
<template>
<div>User Information:</div>
<UserInfo v-once></UserInfo>
</template>
Now that our UserInfo component has the v-once directive attached to it, whenever our component gets rerendered, the UserInfo component will be skipped over and will not be rerendered on the DOM.
With all of these tools at your disposal, you'll now be able to reduce the amount of work that Vue has to perform when users interact with your application, as well as lighten the load of the browser's DOM when it comes to rendering and destroying components.
Happy coding!