AngularJS Development Guidelines

Things to follow while working on AngularJS to have better performance

Please dont use angular batarang (stable) plugin when checking for the watchers. This plugin is not the official one of Batarang. It gives the false count.


  1. One-time binding ( :: ) 

    Angular brings in support for one way binding from 1.3 but there are more things. One-time binding can be used in cases where the expression will not change once the value is set.
    For example:

    one-time binding
     <span>{{::patient.identifier}}</span>

    Since patient's identifier wont change once set, it is a good candidate for one-time binding. Angular doc says,
    "One-time expressions will stop recalculating once they are stable, which happens after the first digest if the expression result is a non-undefined value ". There is a catch here. Watchers will stay alive till the expression is 'undefined'. As long as patient.identifier is undefined, watcher will be alive.

    Another example:

    <span ng-if="::observation.isRequired()" class="asterick">*</span>
    observation.js
       isRequired: function () {
            return this.conceptUIConfig.required;
       },

    In this example, if 'required' property is not present in conceptUIConifg, it will always return undefined and watcher will be alive always. Instead the condition can be changed to  'this.conceptUIConfig.required == true' .

    Also we should pass attributes to directive as one-time binding wherever necessary.
    Example: 

    Passing attributes to directive as one-time binding
    <section address-fields data-address-levels="::addressLevels" data-address="patient.address"
             field-validation="::fieldValidation"></section>

    'address-levels' and 'field-validation' are read only attributes for 'address-fields' directive. i.e. It does not update anything in addressLevels and fieldValidation, so it can be added as one-time binding attributes.

    Example: Here is a real-world example of a commit to improve performance in a display control by switching to one-time binding.


  2. Directive's optional attributes

    Angular directive can have attributes declared as optional using '=?'.
    Lets say, we have a directive called 'attribute-types' with isolated scope

    attributeTypes.js
     scope : {
        targetModel : '=',
        attribute : '=',
        fieldValidation : '=',
        isAutoComplete: '&',
        getAutoCompleteList: '&',
        getDataResults: '&',
        isReadOnly: '&',
        isForm : '='
    }

    And it is used in two different places as follows:

    Usage 1
    <attribute-types target-model="patientProgram.patientProgramAttributes" attribute="attributeType"></attribute-types>
    Usage 2
    <attribute-types target-model="patient" attribute="::attribute" field-validation="::fieldValidation"
                     is-auto-complete="isAutoComplete" get-auto-complete-list="getAutoCompleteList"
                     get-data-results="getDataResults" is-read-only="isReadOnly"></attribute-types>

    Usage 1 passes only two attributes and usage 2 passes more than two. So with the usage 1, attributes other than 'targetModel' and 'attribute' will be undefined. So watchers will be alive for the other attributes 'fieldValidation' and 'isForm'. To avoid this, those attributes can be marked as optional using '=?' instead of '='. When we do that, watcher will be added if attribute is passed and if it is not passed, no watcher will be added to the attribute.

  3.  Use track by with ng-repeat.

    http://www.codelord.net/2014/04/15/improving-ng-repeat-performance-with-track-by/

  4. Disable DebugInfo in production

    Angular by default adds some classes like ng-bindingng-scope, ng-isolate-scope to the DOM elements using jquery data property all the way through DOM hierarchy which takes some significant time while loading. 

    We can disable it in production using 

    app.js
      $compileProvider.debugInfoEnabled(false);

    More info on: https://docs.angularjs.org/guide/production

    This gave us a significant performance boost.
     

  5. Use css class to show and hide small elements instead of ng-if/ng-show

    For example: 

    The arrow that we show in the image is used to indicate whether the section has been expanded or collapsed.
    Instead of hiding and showing it using ng-show like below:

    <div toggle="displayPatientContactDetails">
        <i class="fa fa-caret-right" ng-hide="displayPatientContactDetails"></i>
        <i class="fa fa-caret-down" ng-show="displayPatientContactDetails"></i>  
        <span>Patient Contact Details</span> 
     </div>

    The 'toggle' directive toggles the value of  'displayPatientContactDetails' and adds a class called 'active' to the div based on whether displayPatientContactDetails is true or not . The arrow can be shown or hidden using css of 'active' class.

    div{
      &.active{
        .fa-caret-right{
          display: none;
        }
        .fa-caret-down{
          display: block;
        }
      }
      .fa-caret-down{
        display:none;
      }
    }

    So the html can be changed to:

    <div toggle="displayPatientContactDetails">
        <i class="fa fa-caret-right"></i>
        <i class="fa fa-caret-down"></i>  
        <span>Patient Contact Details</span> 
     </div>


    This is one such example. The same approach can be used for multiple small elements which does not have any data binding or handlers.