Some best practices when building a large Angular application

Recently, we’ve been working on a large Angular app at Lighting Beetle. I’ve made a list of some advice and also some pitfalls, that we encountered.

Always use "track by" in your "ng-repeat"

This is a common performance booster and you should always use it. The purpose of using track by is to force Angular to associate the array item to a DOM element by a key, so when the array is changed (or sorted), the DOM elements can be re-used. If your items in an array don’t have an id, you can use the $index variable instead, however, this will not help when the array is being sorted.

<div ng-repeat="user in users track by user.id"></div>  

Don’t pipe filters

It’s a common way to apply filters in the ng-repeat directive like so:

<div ng-repeat="user in users | myCustomFilter:argument1:argument2 track by user.id"></div>  

However, the filter is being re-run in each $digest cycle (e.g. when the user clicks on something via ng-click), even when the array, or the filtered array hasn’t changed. When the array is large and the filter is heavy, it may cause some delays. If we have some filtering that is required, only when a particular value changes (e.g. when users edit some filter input), we found out that it's better to watch for this change, and call the filtering explicitly, in a controller. Don’t forget to inject the $filter service in your controller first.

$scope.filteredUsers = $filter('myCustomFilter')(/* params */);

Periodically check for memory leaks

We’ve had an issue, when the application was creating huge memory leaks. The leaks were created when switching large views, that represented different application screens. When the user changed the view for 10 times, a more than 100MB big memory leak was introduced, and the numbers just kept growing – that made the application unstable and it froze after some time.

The real problem was, that we had to check our entire application to find a place in our code, where the memory leak is being created. Thus, I recommend to check for memory leaks during the development to prevent them. The one way how to to this is via Chrome Developer Tools, tab Timeline. The common place where the memory leaks are introduced, are directives. When using some third party plugin, or binding some non-angular event listeners, don’t forget to “clean up”, when the directive is removed from the DOM. The Angular emits a special event called ‘$destroy’, when the scope is being destroyed.

angular.module('myApp')  
  .directive('myCustomDirective', function() {
    return {
      link: function(scope, elm, attr) {
        elm.on('click', function(e) {
          // something
        });
        // prevent memory leak
        scope.$on('$destroy', function() {
          elm.off('click');
        });
      }
    };
  });

Keep your $digest cycle fast

This is not really a pitfall, but more like an advice. To make an application responsive enough, you have to keep the $digest cycle as fast as possible. For example, if you used some ng-class directives and they can be replaced by just one, do it! You just saved some time, when $digest cycle will run through your scope. For example, instead of doing this:

<div class="user-info">  
  <div ng-class="{black: user.isLogged}">Jon Snow</div>
  <div ng-class="{grey: user.isLogged}">Steward in the Night's Watch</div>
</div>  

Try using this:

<div class="user-info" ng-class="{'user-info-logged-in': user.isLogged}">  
  <div class="user-name">Jon Snow</div>
  <div class="user-desc">Steward in the Night's Watch</div>
</div>  

Update Aug 20 2014: Another $digest booster is using one-time binding. Check out the official documentation.

Append everything that pop-ups to body.

This is not really an Angular related issue, but our overall experience, while developing application interfaces. We used the Angular UI project to develop features likes modals, tooltips, datepickers, etc.

If an element (e.g. tooltip) is not appended to the body, it is a problem to display the tooltip over an element with overflow:scroll/auto set. My advice is, always use the appendTo: ‘body’ option when using Angular UI, and when developing your own components, keep in mind the issue with element overflow and append to body if it’s possible.

Use custom directives to create components.

If you’ll catch yourself copy-pasting your code, you are breaking the DRY principle. A solution to this problem is to use custom directives, to create reusable and testable components. The source code will be more readable and maintainable. Directives are pretty complicated to learn at the beginning, but are worth it.

Don’t forget to always use isolated scope in your directives, to not mess up your directive scope and the directive’s parent scope. There’s a nice article on isolated scope topic (especially ‘scope in directives’ part).

Organise your file structure by functionality, not by file type

We started the project with organising our source files by type, so we had directories which contained controllers, directories that contained services, or html views. Later, this approach was very confusing for developers who just jumped into the existing project. They needed to edit an existing part of the application, but they had no clue where to search for the source files (where is a controller for this template?)

+-- app
    +-- controllers
    |   +-- login.js
    +-- templates
    |   +-- login.html

The better approach is to organise code by its functionality, and group files in directories that represent e.g. one component, or a part of the application. With this approach, it is very simple to re-use the components even in another application.

+-- app
    +-- login
    |   +-- controller.js
    |   +-- template.html

Conclusion

That’s it, I hope you’ve found this advice useful. You can share your own experience with pitfalls or best practises in Angular in the comments!