Angular JS

Angular Function Declarations


function func1() {
  function foo() {
    return 'foo-top';
  }
  return foo();
}
func1();

The output is foo-top. The function foo is defined and then the function is executed and returned. This one is pretty straight forward


function func2() {
  return foo();
  function foo() {
    return 'foo-bottom';
  }
}
func2();

The output is foo-bottom. When func2 executes it immediately returns the execution of the function foo. But wait, foo hasn't been defined yet? Or has it? Understanding that situation requires some experience with hoisting and the difference between a function declaration and function expression. foo is a function declaration (aka a function statement). JavaScript will hoist function declarations to the top of their closure, in this case that is the top of func2. What's hoisting? Hoisting is when JavaScript moves the definition of some code to the top of the closure. (There will be more on hoisting to follow, so remember that).

function func2() {
  // Hoisted from below
  function foo() {
    return 'foo-bottom';
  }
  return foo();
  // This is hoisted to the top of func2
  //function foo() {
  //  return 'foo-bottom';
  //}
}
func2();

function func3() {
  var foo = function () {
    return 'foo-top';
  }
  return foo();
}
func3();

The output here is foo-top. This examples uses a function expression. A function expression takes a function and sets it to a variable, in this case foo. This works like any other variables defined in JavaScript if it is defined before it is used, then it's value is accessible. That's why we can execute foo successfully in this question.

function func4() {
  return foo();
  var foo = function () {
    return 'foo-bottom';
  }
}
func4();
The output here is an error undefined is not a function. Here is where hoisting comes into play yet again. The return statement executes foo which appears below the return. Since foo is a function expression, it is hoisted above the return right to the top of func4. But wait, didn't I just say it was hoisted up? Then why can't the return execute it properly? The interesting part is that the variable foo is hoisted, but the value it is set to is left in place. Say what? OK, let's look at how it actually is interpreted

/* recommended */
// logger.js
(function () {
   angular
   .module('app')
   .factory('logger', logger);

   function logger () { }
})();

// storage.js
(function () {
   angular
   .module('app')
   .factory('storage', storage);

   function storage () { }
})();


/* avoid */
var app = angular.module('app');
app.controller('SomeController' , SomeController);

function SomeController() { }


/* recommended */
angular
   .module('app')
   .controller('SomeController' , SomeController);

function SomeController() { }

Named vs Anonymous Functions

Using named functions for callbacks produces more readable code, is much easier to debug, and reduces the amount of nested callback code.

/* avoid */
angular
   .module('app')
   .controller('Dashboard', function () { });
   .factory('logger', function () { });


/* recommended */
// dashboard.js
angular
   .module('app')
   .controller('Dashboard', Dashboard);

function Dashboard () { }

// logger.js
angular
   .module('app')
   .factory('logger', logger);

function logger () { }

controllerAs syntax in the view


<!-- avoid -->
<div ng-controller="Customer">
   {{ name }}
</div>


<!-- recommended -->
<div ng-controller="Customer as customer">
   {{ customer.name }}
</div>

Using the controllerAs syntax instead of the classic controller with $scope syntax helps avoid the temptation of using $scope methods inside a controller when it might be better to avoid them or move them to a factory. Consider using $scope in a factory, or if in a controller just when needed. For example when publishing and subscribing events using $emit, $broadcast, or $on consider moving these uses to a factory and invoke form the controller.

Inside the controller, the controllerAs syntax uses this, which gets bound to $scope and is syntactic sugar over $scope. You can still bind to the View and still access $scope methods

/* avoid */
function Customer ($scope) {
   $scope.name = {};
   $scope.sendMessage = function () { };
}


/* recommended - but see next section */
function Customer () {
   this.name = {};
   this.sendMessage = function () { };
}

controllerAs with the view model


/* avoid */
function Customer () {
   this.name = {};
   this.sendMessage = function () { };
}


/* recommended */
function Customer () {
   /* jshint validthis: true */
   var vm = this;
   vm.name = {};
   vm.sendMessage = function () { };
}

/* avoid */
function Sessions() {
   var vm = this;

   vm.gotoSession = function() {
      /* ... */
   };
   vm.refresh = function() {
      /* ... */
   };
   vm.search = function() {
      /* ... */
   };
   vm.sessions = [];
   vm.title = 'Sessions';
}


/* recommended */
function Sessions() {
   var vm = this;

   vm.gotoSession = gotoSession;
   vm.refresh = refresh;
   vm.search = search;
   vm.sessions = [];
   vm.title = 'Sessions';

   function gotoSession() {
      /* */
   }

   function refresh() {
      /* */
   }

   function search() {
      /* */
   }
}

/* avoid */
function Order ($http, $q) {
   var vm = this;
   vm.checkCredit = checkCredit;
   vm.total = 0;

   function checkCredit () {
      var orderTotal = vm.total;
      return $http.get('api/creditcheck').then(function (data) {
         var remaining = data.remaining;
         return $q.when(!!(remaining > orderTotal));
      });
   };
}


/* recommended */
function Order (creditService) {
   var vm = this;
   vm.checkCredit = checkCredit;
   vm.total = 0;

   function checkCredit () {
      return creditService.check();
   };
}


// route-config.js
angular
.module('app')
.config(config);

function config ($routeProvider) {
$routeProvider
  .when('/avengers', {
    templateUrl: 'avengers.html',
    controller: 'Avengers',
    controllerAs: 'vm'
  });
}


No comments:

Post a Comment