Optimizing Angular
Performance
Morgan Stone
@morganstone
Render Time
Tools: ng-stats
STRATEGY:
Minimize/Avoid
Watchers
$watch
scope.$watch('name', function(newValue, oldValue) {
scope.counter = scope.counter + 1;
});
where watches are set
$scope.$watch
{{ }} type bindings (ng-bind too)
Most directives (i.e. ng-show)
Scope variables scope: { bar: '='}
Filters {{ value | myFilter }}
ng-repeat
http://www.alexkras.com/11-tips-to-improve-angularjs-performance/
digest cycle runs on
User action (ng-click etc). Most built in directives will call $scope.apply upon
completion which triggers the digest cycle.
ng-change
ng-model
$http events (so all ajax calls)
$q promises resolved
$timeout
$interval
Manual call to $scope.apply and $scope.digest
http://www.alexkras.com/11-tips-to-improve-angularjs-performance/
ng-if instead of ng-show
<div class="panel" ng-show="panel.isOpen" ng-repeat="panel in panels">
{{panel.name}}
<div class="checkbox" ng-repeat="setting in panel.settings">
<label>
<input type="checkbox" ng-model="setting.on"/>
{{setting.name}}
</label>
</div>
</div>
ng-if instead of ng-show
<div class="panel" ng-show="panel.isOpen" ng-repeat="panel in panels">
{{panel.name}}
<div class="checkbox" ng-repeat="setting in panel.settings">
<label>
<input type="checkbox" ng-model="setting.on"/>
{{setting.name}}
</label>
</div>
</div>
With ng-show, the nodes
remain in the DOM and
are being watched
ng-if instead of ng-show
<div class="panel" ng-if="panel.isOpen" ng-repeat="panel in panels">
{{panel.name}}
<div class="checkbox" ng-repeat="setting in panel.settings">
<label>
<input type="checkbox" ng-model="setting.on"/>
{{setting.name}}
</label>
</div>
</div>
With ng-if, only
nodes visible will
be watched
Bind once
<p>
{{ vm.user }}
</p>
Bind once
<p>
{{ ::vm.user }}
</p>
Once the value is defined,
Angular will render it and
unbind it from the watchers
STRATEGY:
Reduce digest cycle time
track by
<div ng-repeat="model in collection | orderBy: 'id' as
filtered_result">
{{model.name}}
</div>
http://blog.500tech.com/is-reactjs-fast/
Original:
http://speed.examples.500tech.com/ngrepeat/before/angular.html
Adding track by
http://speed.examples.500tech.com/ngrepeat/after/angular.html
track by
<div ng-repeat="model in collection | orderBy: 'id' as
filtered_result">
{{model.name}}
</div>
Without track by ng-repeat
rebuilds all the DOM nodes,
including nodes that
require no change
track by
<div ng-repeat="model in collection | orderBy: 'id' as
filtered_result track by model.id">
{{model.name}}
</div>
track by provides an
index Angular can use
to track model changes
track by
<div ng-repeat="model in collection | orderBy: 'id' as
filtered_result track by model.id">
{{model.name}}
</div>
track by must
be last in the
expression
track by
<div ng-repeat="model in collection | orderBy: 'id' as
filtered_result track by $index">
{{model.name}}
</div>
track by $index
can also provide a
performance boost
piped | filters
ng-repeat=”person in listOfFriends | filter:{
available: true}”
{{ person.birthday | date }}
Angular runs every
single piped filter twice
per $digest cycle
<div class="list-group">
<div
class="list-group-item"
ng-repeat="person in listOfFriends | filter:{ available: true}">
<div class="row">
<div class="col-sm-6">
{{person.name}}
</div>
<div class="col-sm-6">
{{person.birthday | date: 'shortDate' }}
</div>
</div>
</div>
</div>
<div class="list-group">
<div
class="list-group-item"
ng-repeat="person in listOfFriends | filter:{ available: true}">
<div class="row">
<div class="col-sm-6">
{{person.name}}
</div>
<div class="col-sm-6">
{{person.birthday | date: 'shortDate' }}
</div>
</div>
</div>
</div>
Could be done in
controller or
better yet a
service
Keep your controllers
small & leverage
services to do the
heavy lifting
angular
.module('app')
.factory('friendsService', friendsService);
friendsService.$inject = ['$http', '$filter'];
function friendsService($http, $filter) {
var factory = {
getFriends: getFriends,
getAvailableFriends: getAvailableFriends
};
return factory;
function getFriends() {
return $http.get('/someUrl', config)
Injecting $filter
getFriends: getFriends,
getAvailableFriends: getAvailableFriends
};
return factory;
function getFriends() {
return $http.get('/someUrl', config)
.then(successCallback);
function successCallback(friends) {
return friends.map(function(friend) {
friend.birthday = $filter('date')(friend.birthday, 'shortDate');
return friend;
});
}
}
Filter it one place…
in the service
function successCallback(friends) {
return friends.map(function(friend) {
friend.birthday = $filter('date')(friend.birthday, 'shortDate');
return friend;
});
}
}
function getAvailableFriends() {
return getFriends.then(function(friends) {
return friends.filter(function(friend) {
return friend.available;
});
})
}
}
Create additional
methods for
common ng-repeat
| filters
Load Time
STRATEGY:
Reduce # of requests
Caching
$http.get(url, { cache: true}).success(...);
angular.module('docsIsolateScopeDirective', []).directive('myCustomer',
function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
angular.module('docsIsolateScopeDirective', []).directive('myCustomer',
function() {
return {
restrict: 'E',
scope: {
customerInfo: '=info'
},
templateUrl: 'my-customer-iso.html'
};
});
Each directive
templateUrl results
in another request
ui.router
angular.module('abcApp', ['ui.router']).config(function ($stateProvider) {
$stateProvider
.state('user.add', {
url: '/mgt/user/add', templateUrl: 'views/mgt_user/add.html'
})
.state('user.view', {
url: '/mgt/user/view/:userId', templateUrl: 'views/mgt_user/view.html'
})
.state('user.list', {
url: '/mgt/user/list', templateUrl: 'views/mgt_user/list.html'
});
});
and maybe another request
<div class="slide-animate" ng-include="template.url"></div>
which may trigger
another request
Even though template
requests are cached after
GET, they really start to
add up, especially on the
initial load of the app
<script
type="text/ng-template"
id="templateId.html">
<p>This is the content of the template</p>
</script>
You could do this...
But this is ugly and unmaintainable
var myApp = angular.module('myApp', []);
myApp.run(function($templateCache) {
$templateCache.put('templateId.html', 'This is the content of the template');
});
$templateCache
Sorry gramps
But we’re gonna
Javascript our HTMLs
in the BUILD.
Standard Angular build process
Concat & minify all Javascript files into a single file
Combine and minify CSS files into a single file
Gzip & cache assets
Standard Angular build process
Concat & minify all Javascript files into a single file
Combine and minify CSS files into a single file
Gzip & cache assets
Store HTML files into $templateCache
ngHtml2JS
var ngHtml2Js = require('gulp-ng-html2js');
var gulpConcat = require('gulp-concat');
gulp.task('createTemplates', function(cb){
gulp.src(['/app/**/*.html', '/app/**/*.svg'])
.pipe(ngHtml2Js({
base: '/',
moduleName: 'app',
prefix: '/'
}))
.pipe(gulpConcat('templates.'))
.pipe(gulp.dest('./services'))
.on('end', cb);
});
ngHtml2JS
angular.module("app").run(["$templateCache", function ($templateCache) {
$templateCache.put("template/modal/backdrop.html",
"<div class="modal-backdrop fade"n" +
" ng-class="{in: animate}"n" +
" ng-style="{'z-index': 1040 + (index && 1 || 0) + index*10}"n" +
"></div>n" +
"");
}]);
Thank you
mstone@designbymorgan.com
@morganstone

Optimizing Angular Performance in Enterprise Single Page Apps