Speeding up AngularJS apps with simple optimizations
2016-01-07 16:23
411 查看
AngularJS is a huge framework with that already has many performance enhancements built in, but they can’t solve all our problems. No matter how fast the framework, we can all create sluggish code through bad practices and not understanding key concepts that help it perform well. The following performance pointers are some of the things I’ve learned from developing Angular applications that will hopefully enable you to keep building fast applications.
The key concept behind these performance considerations is reducing the number of
This cycle is an internal execution loop that runs through your entire application’s bindings and checks if any values have changed. If values have changed, Angular will also update any values in the Model to return to a clear internal state. When we create data-bindings with AngularJS, we’re creating more
Let’s walk through some quick and easy performance considerations for Angular alongside some code examples that help us reduce
One-time binding syntax
AngularJS dropped a really interesting feature recently in the beta version of 1.3.0: the ability to render data once and let it persist without being affected by future Model updates. This is fantastic news for developers highly concerned with performance! Before this update, we’d typically render a value in the DOM like so:
With the new one-time binding syntax, we introduce a double-colon before our value:
Angular processes the DOM as usual and once the value has been resolved it removes the particular property from it’s internal
It’s known that Angular becomes slower with around 2,000 bindings due to the process behind dirty-checking. The less we can add to this limit the better, as bindings can add up without us really noticing it!
Using the single binding syntax is easy and most importantly fast. The syntax is clear and concise, and a real benefit to lowering the
At some stage in your Angular career, you’ll have stumbled across the
We all use third party plugins, and often the ones we use have their own event system and make DOM updates without Angular knowing. That’s exactly where the
Here’s some pseudo code example usage to demonstrate the concept:
When
Instead of
The only caveat to this approach is that if you’re dependent on two-way binding between Objects from the parent
Avoid
Onto one of the more challenging approaches: avoiding
The
For example, instead of rendering a global navigation using
Be mindful of how many bindings and scopes you’re creating inside templates that become a repeater. Do some quick math and avoid any potential bottlenecks. I often think a little harder about how I can reduce the amount of bindings and scopes before digging into actually writing the code.
An
If you’re doing something like this, it’s time to reconsider:
If you’re building a Directive and some of the logic doesn’t need to rely on a Model, don’t use Angular for it. This logic will live inside the
This gives us better separation with things we actually require from Angular and things we don’t. In the example above, we likely aren’t reliant on Model changes to show a menu as the menu is part of our Directive and can be toggled like normal DOM. Saving
Here’s an example of a DOM filter, these are the slowest type of filter, preprocessing our data would be much faster. If you can, avoid the inline filter syntax.
Angular includes a
A few key points to take away:
Before diving into code, consider how to best architect your application to avoid performance challenges that dirty-checking faces in large quantities of Objects.
Be mindful of ng-repeats -- how much data are you expecting back? How much weight is that going to add to Angular’s
Not everything needs to be “Angular.” In Directives there are many cases we need to work with pure DOM.
Keep checking out the Angular project on GitHub, as there are often some great hidden features that can be found from upcoming releases. That’s how I came across the “bind once” functionality.
The more
For anything else, check the Angular documentation!
The key concept behind these performance considerations is reducing the number of
$$watchersinside Angular to improve the
$digestcycle’s performance, something you’ll see and hear more of as you continue working with Angular. These are crucial to keeping our application state fast and responsive for the user. Each time a Model is updated, either through user input in the View, or via service input to the Controller, Angular runs something called a
$digestcycle.
This cycle is an internal execution loop that runs through your entire application’s bindings and checks if any values have changed. If values have changed, Angular will also update any values in the Model to return to a clear internal state. When we create data-bindings with AngularJS, we’re creating more
$$watchersand
$scopeObjects, which in turn will take longer to process on each
$digest. As we scale our applications, we need to be mindful of how many scopes and bindings we create, as these all add up quickly - each one being checked per
$digestloop.
Let’s walk through some quick and easy performance considerations for Angular alongside some code examples that help us reduce
$$watcherfootprint and understand the
$digestcycle a bit better.
One-time binding syntax {{ ::value }}
AngularJS dropped a really interesting feature recently in the beta version of 1.3.0: the ability to render data once and let it persist without being affected by future Model updates. This is fantastic news for developers highly concerned with performance! Before this update, we’d typically render a value in the DOM like so:<h1>{{ title }}</h1>
With the new one-time binding syntax, we introduce a double-colon before our value:
<h1>{{ ::title }}</h1>
Angular processes the DOM as usual and once the value has been resolved it removes the particular property from it’s internal
$$watcherslist. What does this mean for performance? A lot! This is a fantastic addition to helping us fine tune our applications.
It’s known that Angular becomes slower with around 2,000 bindings due to the process behind dirty-checking. The less we can add to this limit the better, as bindings can add up without us really noticing it!
Using the single binding syntax is easy and most importantly fast. The syntax is clear and concise, and a real benefit to lowering the
$$watcheroverhead. The less work Angular has to do, the more responsive our applications will become.
$scope.$apply()
versus $scope.$digest()
At some stage in your Angular career, you’ll have stumbled across the $scope.$apply()method. It’s often misused in a hopeful, “$scope.$apply will solve my coding between plugins, I’m just going to leave it here” type scenario, which isn’t the best way to use an API. For this reason, it’s misunderstood, but it shouldn’t be as it’s actually quite simple.
$scope.$applyis designed for telling Angular that a Model change has occurred outside of its lifecycle. That’s it. We just call
$scope.$applyto let Angular update itself with those new values. It’s particularly important to remember when to use it correctly, as it’s confused me in the past and thrown uncaught errors in my JavaScript. You’ll get an error thrown from Angular if you’re calling
$scope.$applyin the “wrong” place, usually too high up the call stack.
We all use third party plugins, and often the ones we use have their own event system and make DOM updates without Angular knowing. That’s exactly where the
$scope.$applymethod comes in to help. After these updates occur, calling
$scope.$applykicks off the
$digestloop again and Angular pulls in values that were updated outside of its core.
Here’s some pseudo code example usage to demonstrate the concept:
$(elem).myPlugin({
onchange: function (newValue) {
// model changes outside of Angular
$(this).val(newValue);
// tell Angular values have changed and to update via $digest
$scope.$apply();
}
});
When
$scope.$apply()is called, it kicks the entire application into the
$digestloop and in turn runs
$rootScope.$digest(). This is what actually kicks off the internal
$digestcycle. This cycle processes all of the watchers of the
$scopeit was called from (and its children) until no more listeners can be fired. In simple terms, it traverses all scopes and bindings of your application seeing if things have changed. At first, this process is pretty rapid, but certainly slows over time as the application scales.
Instead of
$scope.$apply, we could turn to
$scope.$digest, which runs the exact same
$digestloop, but is executed from the current
$scopedownwards through its children, a much less costly venture.
The only caveat to this approach is that if you’re dependent on two-way binding between Objects from the parent
$scope, the parent
$scopewon’t be updated until the next
$rootScopefull
$digestcycle. This is because the
$scope.$digestonly descends rather than covering our entire
$scopetree. If you want to update parent
$scopevalues, then unfortunately we can’t use this performance tip and may as well invoke the
$scope.$applyto run the full loop.
Avoid ng-repeat
where possible
Onto one of the more challenging approaches: avoiding ng-repeatwhere we can and where it makes sense. We’ve so far learned that the internals of Angular are pretty intelligent, but we can optimize where we can to help keep it performing well. We’ve learned so far that bindings create a bigger
$digestcycle, and it’d be a great idea for directives we create to potentially be statically rendered components that aren’t tied into Angular unless really needed.
The
ng-repeatdirective is most likely the worst offender for performance concerns, which means it can easily be abused. An
ng-repeatlikely deals with Arrays of
$scopeObjects and this hammers the
$digestcycle’s performance.
For example, instead of rendering a global navigation using
ng-repeat, we could create our own navigation using the
$interpolateprovider to render our template against an Object and convert it into DOM nodes.
Be mindful of how many bindings and scopes you’re creating inside templates that become a repeater. Do some quick math and avoid any potential bottlenecks. I often think a little harder about how I can reduce the amount of bindings and scopes before digging into actually writing the code.
More DOM manipulation in Directives
Another offender that will increase$$watchercounts are the core Angular directives such as
ng-showand
ng-hide. Although these might not immediately increase watcher counts dramatically, they can easily stack up in the hundreds inside an
ng-repeat.
An
ng-repeatleads to an increasing amount of
$$watcherswhich may only serve a tiny purpose but are constantly looped over by Angular - things such as
trueand
falsetoggling to activate
ng-showand
ng-hide. We can aim to remove these where it might make sense to.
If you’re doing something like this, it’s time to reconsider:
<div ng-show=”something”></div>
$scope.something = false;
$scope.someMethod = function () {
$scope.something = true;
};
If you’re building a Directive and some of the logic doesn’t need to rely on a Model, don’t use Angular for it. This logic will live inside the
linkcallback; under no circumstance write DOM manipulation logic in a Controller! There are plenty of Directives floating around that set a
$scopevalue to “true” and back to “false” to show and hide content, when a built-in
.hide()and
.show()call would be best suited. Angular also provides us with Directives such as
ng-mouseenter, these can be more costly too as they’re not only binding an event listener, they become a part of the
$digestcycle adding to the application weight. Inside the
linkcallback, we should advocate the use of
addEventListeneror jQuery’s “on” method.
var menu = $element.find(‘ul’);
menu.hide();
$scope.someMethod = function () {
menu.show();
};
This gives us better separation with things we actually require from Angular and things we don’t. In the example above, we likely aren’t reliant on Model changes to show a menu as the menu is part of our Directive and can be toggled like normal DOM. Saving
$$watcherssaves us any performance bottlenecks later on!
Limit DOM filters
Filters are really simple to use, we insert a pipe, the filter name and we’re done. However, Angular runs every single filter twice per$digestcycle once something has changed. This is some pretty heavy lifting. The first run is from the
$$watchersdetecting any changes, the second run is to see if there are further changes that need updated values.
Here’s an example of a DOM filter, these are the slowest type of filter, preprocessing our data would be much faster. If you can, avoid the inline filter syntax.
{{ filter_expression | filter : expression : comparator }}
Angular includes a
$filterprovider, which you can use to run filters in your JavaScript before parsing into the DOM. This will preprocess our data before sending it to the View, which avoids the step of parsing the DOM and understanding the inline filter syntax.
$filter('filter')(array, expression, comparator);
Summing up
These Angular performance tips have helped me develop applications better, with more structure and more thinking behind the code before I get stuck in. It’s not premature optimization when the Angular team provides us with great new APIs to help our apps perform better: they listen, they build, they release. It’s up to us to keep pushing Angular’s reputation forward by building lightning-fast and responsive applications.A few key points to take away:
Before diving into code, consider how to best architect your application to avoid performance challenges that dirty-checking faces in large quantities of Objects.
Be mindful of ng-repeats -- how much data are you expecting back? How much weight is that going to add to Angular’s
$digestcycle?
Not everything needs to be “Angular.” In Directives there are many cases we need to work with pure DOM.
Keep checking out the Angular project on GitHub, as there are often some great hidden features that can be found from upcoming releases. That’s how I came across the “bind once” functionality.
The more
$$watchersthere are, the slower your application will be, and with some of the performance enhancements above, even their simplicity can make a huge difference
For anything else, check the Angular documentation!
相关文章推荐
- Android官方文档阅读之旅——Device Compatibility
- [Unity 3D教程]教你如何在3D场景中选择物体并显示轮廓效果
- IOS应用在iPhone5和iPhone5s上不能全屏显示,应用画面上下各有1条黑色的解决方案
- iOS 数据持久化的几种方式
- 动态适应label的高度
- unity3D中一些小功能的实现
- Android ComponentName的用法
- iOS 键盘遮挡问题
- ubuntu 安装 swift 64位
- Android studio测试使用
- Swift 中的委托/代理模式(转载)
- iOS中,如何做到未知参数数量的反射
- Swift 调用oc 桥接头文件
- 转载自简书iOS 异步绘制相关
- Swift中XMPP的简单使用
- Swift Strings and Characters
- iOS json字段转属性
- Swift中自定义Cell
- Android中Parcelable用法
- Swift场景过渡总结