ngAdvanced

mohuk

mohukh

It is really important to have the right code in the right place

12 ways you can improve your Angular JS code

 The rule of 1

  • 1 component per file
  • 1 purpose served by each component
  • 1 purpose served by each function

 

e.g.

  • Controllers - View logic 
  • Factories - Business Logic and Models
  • Directives - DOM manipulation

 The rule of 1

(function(){

    'use strict';

    angular.module('app')
        .controller('Warlords', Warlords);

    function Warlords(){
        var vm = this;
    }


}());
(function(){
    'use strict'
    
    angular.module('app')
        .controller('Warlords', Warlords);
    
    function Warlords(){
        ...
    }

    angular.module('app')
        .controller('Warchiefs', Warchiefs);
    
    function Warchiefs(){
        ...
    }
    
    //controllers galore
    ...
    ...
}());

1 component per file

 The rule of 1

(function(){

    'use strict';

    angular.module('app')
        .controller('Warlords', Warlords);

    function Warlords($http){
        var vm = this;
        init();

        function init(){
            $http.get('warlords/orcs')
                .then(function(res){
                    if(res.status === 200){
                        res.data.forEach(function(){
                            //do some business manipulation here
                        });
                    }
                    
                })
        }
    }


}());

1 purpose for each component 

 The rule of 1

1 purpose for each component 

(function(){

    'use strict';

    angular.module('app')
        .controller('Warlords', Warlords);

    function Warlords(battalion){
        var vm = this;
        init();
        
        //delegate logic to factory
        //controller should not know how warlords are being fetched
        //meta checking should be obscured from the controller
        function init(){
            battalion.warlords()
                .then(function(warlords){
                    vm.warlords = warlords;
                });
        }
    }


}());

"The" directory structure

  • Locate files easily
  • Identifiable at a glance
  • Flat structure
  • Try to stay DRY

"The" directory structure

app
|-- scripts
|---- controllers
|------ warlords.js
|------ warcheifs.js
|------ chieftains.js
|---- services
|------ dataservice.js
|------ chieftains.js
|---- directives
|---- filters
|---- app.js
|-- index.html
app
|-- battalions
|---- warlords.controller.js
|---- cheiftains.controller.js
|---- clan.factory.js
|---- battalion.module.js
|---- warcry.filter.js
|-- barracks
|---- barracks.controller.js
|---- troops.factory.js
|---- build.directive.js
|---- barracks.module.js
|-- index.html

by type

by feature

"The" directory structure

app
|-- battalions
|---- controllers
|------ warlords.controller.js
|------ cheiftains.controller.js
|---- services
|------ clan.factory.js
|---- filters
|------ warcry.filter.js
|---- battalion.module.js
|-- index.html

Best of both worlds?

Using ControllerAs

<div ng-controller="Trolls as vm">
    <div ng-repeat="axthr in vm.axeThrowers">
        <span ng-bind="axthr.name"></span>
    </div>
</div>

<script type="text/javascript">
    (function(){
        'use strict';
        angular.module('app')
            .controller('Trolls', Trolls);
            
        function Trolls(troops){
            var vm = this;
            troops.axeThrowers()
                .then(function(axthrws){
                    vm.axeThrowers = axthrws;
                });
        }
    }())
</script>

Using ControllerAs

  • Eliminates $scope soup
  • cleaner code
  • reduces misuse of unnecessary constructs
  • nearer to actual JavaScript
  • Allows nesting of controllers easily **

Using ControllerAs

<div ng-controller="Trolls as vm">
    <div ng-controller="Orcs as orcs">
        <div ng-repeat="axthr in vm.axeThrowers" ng-if="orcs.show">
            <span ng-bind="axthr.name"></span>
        </div>
    </div>
</div>

Using ControllerAs

(function(){

    'use strict';

    angular.module('app')
        .config(function($routeProvider){
            $routeProvider
                .when('/battle', {
                   templateUrl: 'battleGround.html',
                    controller: 'Battle as vm' 
                });
        });

}());

Factories vs Services vs Providers, may the best construct win

Factory

(function(){

  'use strict';

  angular.module('app')
    .factory('toArms', toArms);

    function toArms(){
      return {
        scream: scream
      };

      function scream(){
        //some implementation
      }
    }
}());

Service 

(function(){

  'use strict';

  angular.module('app')
    .service('toArms', toArms);

    function toArms(){
      this.scream = scream;

      function scream(){
        //some implementation here
      }
    }
}());

Provider

(function(){

  'use strict';

  angular.module('app')
    .service('toArms', toArms);

    function toArms(){

      var weapons = ['sword', 'axe', 'bubblegum'];

      this.setWeapons = function(weaps){
        weapons.concat(weaps);
      }

      this.$get = function(){
        return {
          weapon: weapon
        };

        function weapon(){
          //some logic to get random weapon
        }
      }
    }
}());

Provider

(function(){

  'use strict';

  angular.module('app')
    .config(configuration);

    function configuration(toArmsProvider){
      toArmsProvider.setWeapons(['rifles', 'mage wands']);
    }
}());
(function(){

  'use strict';

  angular.module('app')
    .controller('Trolls', Trolls);

    function Trolls(toArms){
      //returns sword, axe, 
      //bubblegum, rifles, mage wands
      toArms.weapons();
    }
}());

Verdict

  • Use Factory, it will suffice for your needs
  • Build providers when you need a configurable instance of a service

Reinventing the Wheel ... Not: Decorator Services

Decorator Services

(function() {
  "use strict";
 
    angular
      .module( 'app', [ ] )
      .config([ "$provide", function($provide)
      {
            $provide.decorator( '$log', [ "$delegate", function($delegate)
            {
                // Save the original $log.debug()
                var debugFn = $delegate.debug;
 
                $delegate.debug = function()
                {
                  var args    = [].slice.call(arguments),
                      now     = DateTime.formattedNow();
 
                  // Prepend timestamp
                  args[0] = supplant("{0} - {1}", [ now, args[0] ]);
 
                  // Call the original with the output prepended with formatted timestamp
                  debugFn.apply(null, args)
                };
 
                return $delegate;
            }]);
      }]);
 
})();

Decorator Services

  • Mutate services at config time
  • Consolidate behavior at a single place

Error Handling: HTTP Interceptors

$httpProvider.interceptors.push(function($q, httpStatusCodes) {
  return {
   'request': function(config) {
       // intercept the request to do some mumbo jumbo
    },

    'response': function(response) {
       // intercept the response to perform some abra kadabra
       // you can perform data extraction from here and resolve the chain of promises by return $q.when(data)
       // you can also reject the chain of promises from here by return $q.reject(reason)
    },
    'responseError': function(response){
      // intercept the response error to perform some clean up
      // reject the chain of promises and perform a logout operation
      // you can leave the promise dangling and perform a logout as well
    }
  };
});

Error Handling: HTTP Interceptors

  • Define a single entry/exit point for your app
  • Consolidate the logic for HTTP error handling and custom status codes at one place
  • Resolve, reject and ignore the chain of promises as needed in the application

One time bindings

<!-- Two way binding -->
<span ng-bind="eclipse"></span>

<!-- One time binding -->
<span ng-bind="::lunarBeam"></span>
  • Attaches a watcher to the variable and removes it once it is defined
  • Watcher remains until the variable is undefined

One time bindings

  • Reduces the number of watchers on a view
  • Makes dirty checking lighter and faster
  • Is generally used to dynamically set the init state of the view

ngShow/Hide vs ngIf

  • ngShow/Hide does the necessary through CSS
  • ngIf removes elements from the DOM tree

 

 

ngShow/Hide

  • More performant
  • Use where there is very frequent show/hide
  • Any handlers attached to DOM would still be registered and might not show if hidden

 

 

ngIf

  • Less performant
  • Avoid using to only show/hide
  • Use where you want to remove DOM from the view as it is no longer valid
  • Can be combined with one-time bindings to set the state of the view

 

Production Ready Configuration

angular.module('app')
    .config(['$compileProvider', function ($compileProvider) {
      $compileProvider.debugInfoEnabled(false);
    }]);

Removes all the debug info which is required in development for a significant performance gain

Nested and Named Routing with UI Router

The missing router in Angular JS. Lets dive into Angular UI Router docs.

Nested and Named Routing with UI Router

  • Currently the best routing solution available
  • Supports nested, named views
  • Very easy to support deep-linking

Use a style guide, static analysis and scaffolding tools

  • John Papa's Style Guide
  • JSHint, JSLint, ESLint, JSCS
  • Yeoman

Use a style guide, static analysis and scaffolding tools

  • Ensures code quality
  • Easier for developers to join the team and quickly start development
  • Easier to maintain code with clear guidelines
  • Scaffolding reduces chances of errors and saves time
  • Don't restrict yourself to limits of a tool when scaffolding 

Integrate static analysis and styling into build process

  • Grunt
  • Gulp

Response to my email?

anyone ?

Code like Shahid Bhai

Credits: @John_Papa

ngAdvanced

By Mohammad Umair Khan

ngAdvanced

A deeper dive into architecting Angular JS apps

  • 1,152