Tuesday, November 3, 2015

AngularJS - Best practices

Introduction:
This a quick guide for myself to check/review what pattern/style/standard should be followed during AngularJS application development. Please check the resources section for the links where the gurus have explained elaborately the reason behind each item in this post. Once someone gets the head around the explanations or becomes more advanced developer, this post will serve as a quick cheat sheet for a refresher and reference. Added a facts section which would help going through the practices to understand better.

Some quick facts:
  1. All services (value, factory, service) in AngularJS are Singleton 
  2. Service services are instantiated with the new keyword, need to use this for public methods and variables
  3. Factory services expose it's members (variable, function) using return{} defined in it
Practices grouped by component:

ComponentPractices to follow
Module1 - 11
ControllerModule + (12, 13, 15, 16, 17, 18)
ServiceModule + (14, 19)
DirectiveModule + (20, 21, 22, 23, 24, 25)
RouterModule + (18)


Best practices items:
  1. Create separate file for each controller/service/router/directiveReason: code separation, better testability of code, developers work on independently without being affected by others
  2. File name should follow - ..js
  3. Test file should use spec suffix. ..spec.js
  4. Application Structure should follow LIFT(Locate, Identify, Flat, Try to be DRY) Principle.
    1. Locating our code is easy 
    2. Identify code at a glance 
    3. Flat structure as long as we can 
    4. Try to stay DRY (Don’t Repeat Yourself) or T-DRY
  5. Use unique naming conventions for modules and with separators for sub-modules
    Reason: help avoid module name collisions
  6. Put any member variables in any module on top
    Reasonhelps identify instantly what members are defined and used in the implementation flow
  7. Use named functions instead of passing an anonymous function in as a callback. May ideal to use on major functions
    Reasoneasy to debug, reduces the amount of nested callback code, increases code readability
  8. Declare any function in any module at the bottom. JavaScript anyway hoists the function definition at the top during execution
    Reason: helps separate the implementation detail from the flow and increases readability
  9. Use functional declaration over functional expression (declare and assign to a var)
    Reason: removes the concern over using a function before it is defined. Code organization (moving a block of code up or down) won't break anything
  10. Use IIFE scoping. Ideal solution should be creating build task to wrap the concatenated files inside an IIFE. IIFE - Immediately Invoked Function Expression.
    Reason:to avoid polluting the global scope with custom function declarations
  11. Consider manual annotation for dependency injection
    Reason: to make the code safe of minification
  12. Use the 'controller as' syntax over the classic $scope. Need to follow below steps for that - 
    1. Use 'controller as' in view (HTML). Like - 'MyController as ctl'
    2. Refer any members using the controller reference. Like - 'ctl.aValue'
    3. In controller, use a capture variable and associate members to the capture variable. Like - 'var vm = this; vm.aValue = 'A Value';'

      Reason
      avoids any reference issues, gives way to write code on contextual manner, easier to read
  13. Put bindable variables on top
    Reasonhelps identify instantly which members of the controller can be bound and used in the View
  14. Put member variables on top
    Reasonhelps identify instantly which members can be called and must be unit tested
  15. Defer logic in a controller by delegating to services and factoriesReason: Controller would be slimmer where implementation is hidden. Allows logic to be reused by multiple modules (specially by other controllers) and easier for unit testing
  16. Use one controller per view. If any logic needs to be reused, better to move it to a factory/service and use it from each separate controller
    Reason: Controller will be slimmer, writing unit test will be simpler
  17. Should use prototype to extend a controller and Object.create to create new object
  18. When a controller must be paired with a view and either component may be re-used by other controllers or views, define controllers along with their routes (check this)
  19. Use this for public methods and variables as services are instantiated with the new keyword
  20. Consider creating a directive for any DOM manipulation. If alternative ways can be used such as using CSS to set styles or the animation services, Angular templating, ngShow or ngHide, then use those instead.
    Reason: DOM manipulation can be difficult to test, debug
  21. Use unique and short directive prefix
    Reason: helps identify the directive's context and origin
  22. Use restrict E for standalone element represented by the directive
  23. Use restrict A when the directive is enhancing a DOM element's attribute
  24. Avoid ng- as these are reserved for Angular directives
  25. Directives normalization
    1. Prefer using the dash-delimited format (e.g. ng-bind for ngBind). To use an HTML validating tool, data-prefixed version can be used (e.g. data-ng-bind for ngBind).
    2. Prefer using directives via tag name and attributes over comment and class names.
      Reason: easier to determine what directives a given element matches
  26. Directive creation
    1. Prefer using the definition object over returning a function
    2. prefix own directive names
      Reason: to avoid any future collisions
    3. Unless template is very small, it's typically better to break it apart into its own HTML file and load it with the templateUrl option
  27. Directives should clean up after themselves. Use $element.on('$destroy', ...) or $scope.$on('$destroy', ...) to run a clean-up function when the directive is removed
  28. use controller when want to expose an API to other directives. Otherwise use link.
  29. Use angular wrapper services. Like $document, $window, $timeout, $interval etc. instead of using native.
    Reason: easily testable rather mocking native ones by yourself
Few Additional items: these items are more like personal preference. Though the Angular gurus (John Papa, Todd Motto) are more used to doing these or suggesting to do so.
  1. Consider separate line to inject dependencies rather in-line dependencies
    Reason: long list of dependencies makes it harder to read. Little confusing as last item in the array is a function rather string value
  2. Use ng-annotate for Gulp or Grunt and comment functions that need automated dependency injection using /* @ngInject */.
  3. Tool usage:
    1. Use Jasmine or Mocha for testing
    2. Use protractor for E2E (End to End) testing 
    3. Use karma for test runner
    4. Use Sinon for stubbing and spying
    5. Use headless browser like PhantomJS
    6. Use JSHint as code analyzer 



Resources:
1. https://github.com/johnpapa/angular-styleguide
2. https://github.com/toddmotto/angularjs-styleguide
3. http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
4. http://www.johnpapa.net/angular-function-declarations-function-expressions-and-readable-code/

No comments: