Optimise your Angular 1.x app: Fast rendering with HyperScript and virtual-dom

While AngularJS is certainly not a slow framework, there are cases where dealing with large amounts of data could cause a significant performance degradation, particularly when we render a lot of it on the page.

For those not familiar with the issue: every ng- attribute and element, {{expression}} and $scope.$watch contributes to slowing down the $digest cycle, since Angular creates a lot of “watchers” to check that data for changes. This is especially true when we frequently invoke costly functions from the template and use multiple ng-repeats to render large lists of items.

There is only so much that you can do to reduce the number of $$watchers on a page. You can use the one-time binding (::) syntax, track by with ng-repeat and de-register $scope.$watch functions when not needed, and yet you can still end up with thousands of watchers and a slow digest cycle, making the app pretty unresponsive and sluggish.

When everything fails, there is still a solution to improve the performance in your app—and even make it pretty damn fast!

Note: you can use HyperScript and virtual-dom for rendering in any JavaScript application; however, this method is particularly relevant for AngularJS 1.x applications, since Angular 2, React and Ember 2.x have their own virtual DOM implementations built-in.

From templates to pure DOM

The truth is: not everything needs to be done using Angular. When you need performance above all, you cannot beat rendering with plain JavaScript. This doesn’t mean moving away from Angular altogether and going back to the old jQuery days — you can convert only specific templates/directives and keep the rest of the application the same! Consider the following (quite simple, but still…) example:

Let’s say that totalItems is super slow — it contains a lot of items, that change frequently (maybe it’s a real-time application, constantly streaming data), and all the filtering and ordering doesn’t help. In addition, there’s the myMagicFilter filter that’s also slowing things down, and the final result is lots of bindings to the $digest cycle.

The template logic above can be refactored to a controller or a directive (notice that I’ve injected $filter into the code) and rendered directly to the page:

With a few extra lines of code, you’ve eliminated all those extra watchers — good job! Put that rendering code into a pure function and call it whenever you need to render the data.

In reality, however, our templates are much more complicated than this. They tend to contain not just deeply nested HTML, but also a lot of logic (all those ng-if, ng-hide, ng-class, etc). Using the DOM API to create elements manually can quickly become tedious and the resulting code will be hard to maintain.

Enter HyperScript!

From pure DOM to HyperScript & v-dom

If you want to generate HTML with JavaScript, HyperScript is a great option if you like functional, concise and maintainable code. It comes to about 7kb minified (and probably around 3kb gzipped) and it’s quite intuitive to use, especially if you have used Haml, Jade or a similar template language before.

Note: if you don’t want to import the whole virtual-dom package, you can import virtual-dom/h and virtual-dom/create-element separately.

Here is how HyperScript generally looks like. Note that I’m using a module bundler (Webpack) with an ES2015 transpilation step.

The HyperScript h() function generates a Virtual DOM tree. We can then use createElement() to convert the tree to HTML and insert it into the page. Why Virtual DOM instead of just HTML? You will see why in a second—first, let’s convert the example earlier from pure DOM to HyperScript! Since we can pass arrays of elements to the h() function, this is a perfectly valid HyperScript example:

So Example 2 could be easily re-written as:

What about virtual-dom?

Since our applications change frequently, we want to update the UI as fast and as efficiently as possible. With virtual-dom, we can simply re-render everything (just like in React) using the diff and patch functions, like so:

And that’s it! You just made your app render as fast as anything out there, and you didn’t introduce any fat libraries and a few million dependencies.

If you are not familiar with the concept of Virtual DOM and how it works internally, I strongly recommend reading this answer on StackOverflow.

Downsides

There are pretty much none, but there is one important detail: if you are using a lot of custom or third-party directives in your templates, you will have to refactor their logic to Angular services or pure JavaScript functions, so that you can use them when generating the vdom from the controller. That could be quite an overhead, but if you think about it — all these directives are what is probably causing your performance problems anyway, so that’s for the better!

Conclusion

Complex template logic could be refactored to pure JavaScript and put into controllers/directives to speed up the $digest cycle. With HyperScript, you can make your templating more concise, functional, and leverage super fast rendering with virtual-dom. And at the same time — you can keep using Angular’s routing, controllers, dependency injection and all other goodness!

I’m Svilen — a full-stack web developer and co-founder at Heresy. We’re always looking for engineers who enjoy working with the latest technologies and solving challenging problems. If you’re curious, check out jobs page!