Thursday, July 30, 2015

What does _.bind() do?

The name, "bind", conjures fear.  It's scary.  Binding sounds mysterious and strange.

Underscore.js has the _.bind() function.  What does it do?

Let's say that you using the JavaScript setTimeout() function to set the focus on a jQuery DOM element.

var el = $('#myinput');
setTimeout(function() { el.focus(); }, 50);

The setTimeout() function takes a standalone function as its first argument.

Now, consider this code:

var el = $('#myinput');
setTimeout(el.focus, 50);

This code doesn't work.  Why?  Well, the focus() call is an object method, not a standalone function.  The setTimeout() function expects a standalone function, not an object method.

A standalone function and an object method aren't the same thing.  If you pass an object method as an argument that expects a standalone function, it's not the same thing, it's wrong and it doesn't work.

It's as if you passed a string as an argument to a function that expects a number.  You passed the wrong kind of thing, even if the string contains a number.  If you want it to work, you have to convert the string to the proper type before calling the function.

So, how can you convert an object method call into a standalone function?

One way is to create a standalone function that calls the object method as you did in the original code.

A second way is to call the _.bind() function.

The _.bind() function says, "Create a standalone function that invokes the method in the first argument, using the second argument as the this object."

So, instead of creating your own standalone function, the _.bind() function will create one for you.

var el = $('#myinput');
setTimeout(_.bind(el.focus, el), 50);

The _.bind(el.focus, el) code creates a standalone function that calls the el.focus() method.  You can think of it as converting a method (call) into a standalone function.

The _.bind() implementation is more complex but you can understand it by imagining that it is implemented like this:

function bind(method, self) {
  return function() {
    self.method(); // this doesn't work; it's just for clarity
  };
}

That's what _bind.() does.  It takes a method call and "converts" it into a standalone function.

Once you grasp this, you will discover that _.bind() has additional uses, such as converting a standalone function call with multiple arguments into a standalone function with no arguments.

With this explanation, I hope that _.bind() and binding is no longer mysterious and strange.  And not scary.


AngularJS dependency resolution and child controller creation

Suppose that you have an AngularJS template (HTML with AngularJS tags, same thing) and an AngularJS controller and you want to execute them together:

var elem = angular.element(angularTemplateHtml);
var compileFunc = $compile(elem);
$controller(controllerName, locals);
elem = compileFunc($scope);

This is how ngRoute bootstraps a route.

Suppose that the controller has dependencies.  How are they resolved?

Deep inside angular.js, there is an invoke() function that looks like this:

function invoke(fn, self, locals, serviceName) {
  if (typeof locals === 'string') {
    serviceName = locals;
    locals = null;
  }
  var args = [],
    $inject = createInjector.$$annotate(fn, strictDi, serviceName),
    length, i,
    key;

  for (i = 0, length = $inject.length; i < length; i++) {
    key = $inject[i];
    if (typeof key !== 'string') {
      throw $injectorMinErr('itkn',
        'Incorrect injection token! Expected service name as string, got {0}', key);
    }
    args.push(
      locals && locals.hasOwnProperty(key)
      ? locals[key]
      : getService(key, serviceName)
    );
  }
  if (isArray(fn)) {
    fn = fn[length];
  }
  // http://jsperf.com/angularjs-invoke-apply-vs-switch
  // #5388
  return fn.apply(self, args);
}

AngularJS looks for dependencies in the locals object and getService().  If it doesn't find the dependency in either place, the dependency fails and you get an error.

It's interesting that you can bolt dependencies onto the locals object.  The locals object is passed directly into the $controller() function so, if you are calling $controller() function directly, you can provide dependencies to the controller, even if those dependencies aren't AngularJS services.

The $controller() function itself looks like this:

/**
 * @ngdoc service
 * @name $controller
 * @requires $injector
 *
 * @param {Function|string} constructor If called with a function
 * then it's considered to be the controller constructor function.
 * Otherwise it's considered to be a string which is used to
 * retrieve the controller constructor using the following steps:
 *
 *  * check if a controller with given name is registered via 
 *    `$controllerProvider`
 *  * check if evaluating the string on the current scope returns
 *    a constructor
 *  * if $controllerProvider#allowGlobals, check
 *    `window[constructor]` on the global `window` object (not
 *    recommended)
 *
 * The string can use the `controller as property` syntax, where
 * the controller instance is published as the specified property
 * on the `scope`; the `scope` must be injected into `locals` param
 * for this to work correctly.
 *
 * @param {Object} locals Injection locals for Controller.
 * @return {Object} Instance of given controller.
 *
 * @description
 * `$controller` service is responsible for instantiating
 * controllers.
 *
 * It's just a simple call to {@link auto.$injector $injector}, but
 * extracted into a service, so that one can override this service
 * with [BC version](https://gist.github.com/1649788).
 */
return function(expression, locals, later, ident) {
  // PRIVATE API:
  //   param `later` --- indicates that the controller's constructor
  //     is invoked at a later time. If true, $controller will
  //     allocate the object with the correct prototype chain, but
  //     will not invoke the controller until a returned callback is
  //     invoked.
  //   param `ident` --- An optional label which overrides the label
  //     parsed from the controller expression, if any.
  var instance, match, constructor, identifier;
  later = later === true;
  if (ident && isString(ident)) {
    identifier = ident;
  }

  if (isString(expression)) {
    match = expression.match(CNTRL_REG);
    if (!match) {
      throw $controllerMinErr('ctrlfmt',
        "Badly formed controller string '{0}'. " +
        "Must match `__name__ as __id__` or `__name__`.",
        expression);
    }
    constructor = match[1],
    identifier = identifier || match[3];
    expression = controllers.hasOwnProperty(constructor)
      ? controllers[constructor]
      : getter(locals.$scope, constructor, true) ||
        (globals ? getter($window, constructor, true) : undefined);
        assertArgFn(expression, constructor, true);
  }

  if (later) {
    // Instantiate controller later:
    // This machinery is used to create an instance of the object
    // before calling the controller's constructor itself.
    //
    // This allows properties to be added to the controller before
    // the constructor isinvoked. Primarily, this is used for
    // isolate scope bindings in $compile.
    //
    // This feature is not intended for use by applications, and is
    // thus not documented publicly.
    // Object creation: http://jsperf.com/create-constructor/2
    var controllerPrototype = (isArray(expression) ?
      expression[expression.length - 1] : expression).prototype;
    instance = Object.create(controllerPrototype || null);

    if (identifier) {
      addIdentifier(locals, identifier, instance, constructor
        || expression.name);
    }

    var instantiate;
    return instantiate = extend(function() {
      var result = $injector.invoke(expression, instance, locals,
        constructor); // resolve dependencies
      if (result !== instance && (isObject(result)
        || isFunction(result))) {
        instance = result;
        if (identifier) {
          // If result changed, re-assign controllerAs value to
          // scope.
          addIdentifier(locals, identifier, instance, constructor
            || expression.name);
        }
      }
      return instance;
    }, {
      instance: instance,
      identifier: identifier
    });
  }
  instance = $injector.instantiate(expression, locals, constructor);

The $controller() function's first argument is expression.   This is either a controller object or its a string with the controller's name.  If it's a string with the controller's name, the $controller() function immediately looks up the controller object and sets expression equal to the object.

Then, the $controller() function chooses whether to create the object instance and return the constructor function to be called later (in green text) or to create the object instance and call the constructor immediately.

In our example, the constructor is called immediately.

But what happens if angularTemplateHtml contains additional controllers that are created by using the ng-controller attribute?

When $compile() function is invoked, it crawls through the angularTemplateHtml DOM tree using functions named nodeLinkFn()childLinkFn() and compositeLinkFn() to find and instantiate AngularJS constructs, like controllers.

The compileFunc() function is created and returned by the $compile() call.

Inside AngularJS, the compileFunc() function looks like this:

return function publicLinkFn(scope, cloneConnectFn, options) {
  assertArg(scope, 'scope');

  options = options || {};
  var parentBoundTranscludeFn = options.parentBoundTranscludeFn,
    transcludeControllers = options.transcludeControllers,
    futureParentElement = options.futureParentElement;

AngularJS crawls the angularTemplateHtml DOM tree during the $compile() function and, when it finds a ng-controller attribute, it invokes the setupControllers() function.

function setupControllers($element, attrs, transcludeFn,
    controllerDirectives, isolateScope, scope) {
  var elementControllers = createMap();
  for (var controllerKey in controllerDirectives) {
    var directive = controllerDirectives[controllerKey];
    var locals = {
      $scope: directive === newIsolateScopeDirective
        || directive.$$isolateScope ? isolateScope : scope,
      $element: $element,
      $attrs: attrs,
      $transclude: transcludeFn
    };
    var controller = directive.controller;
    if (controller == '@') {
      controller = attrs[directive.name];
    }

    var controllerInstance = $controller(controller, locals, true,
      directive.controllerAs);

Notice the bold purple text where the locals object is created by AngularJS when it finds an ng-controller attribute.  While a child controller can access its parent's $scope for various purposes, a child controller's locals object is hardcoded, unavailable and unlinked to the parent controller's locals object!  So, a parent controller cannot resolve dependencies for a child controller created by AngularJS.  Wouldn't it be nice if a parent controller could use its locals object to provide dependency resolution and control over instantiation of its child controllers?  But, it doesn't.  Maybe next version.

Notice the orange highlighted true argument inside the setupControllers() function.  The true argument allocates the controller instance but does not invoke the constructor immediately.  It returns the constructor to be invoked later.

AngularJS allocates controller instances during the $compile() call (in our example) but waits and invokes constructors later during the compileFunc() call (in our example).

During the compileFunc() call, the child controller constructor functions are invoked in this code:

if (elementControllers) {
  // Initialize bindToController bindings for new/isolate scopes
  var scopeDirective = newIsolateScopeDirective
    || newScopeDirective;
  var bindings;
  var controllerForBindings;
  if (scopeDirective && elementControllers[scopeDirective.name]) {
    bindings = scopeDirective.$$bindings.bindToController;
    controller = elementControllers[scopeDirective.name];

    if (controller && controller.identifier && bindings) {
      controllerForBindings = controller;
      thisLinkFn.$$destroyBindings = 
        initializeDirectiveBindings(scope, attrs,
        controller.instance, bindings, scopeDirective);
    }
  }
  for (i in elementControllers) {
    controller = elementControllers[i];
    var controllerResult = controller();

    if (controllerResult !== controller.instance) {
      // If the controller constructor has a return value,
      // overwrite the instance from setupControllers and update
      //the element data
      controller.instance = controllerResult;
      $element.data('$' + i + 'Controller', controllerResult);
      if (controller === controllerForBindings) {
        // Remove and re-install bindToController bindings
        thisLinkFn.$$destroyBindings();
        thisLinkFn.$$destroyBindings =
          initializeDirectiveBindings(scope, attrs,
            controllerResult, bindings, scopeDirective);
      }
    }
  }
}

Notice the bold red text shows where the child controller construction functions are invoked during the compileFunc() call.

This shows the lifecycle of controller objects, both directly created controllers and controllers created by AngularJS itself.

Monday, July 27, 2015

AngularJS $formatters

Suppose you have a new directive that declares a tag like:

<input type="array">

In AngularJS v1.4.1, when you add an "input" directive, AngularJS decides how its going to store your data.  Will your data be stored as a string (in a text node), in a JavaScript object (hanging off a DOM node) or what?

In the case above, is the data stored as a string like "[1, 2, 3, 4]" in a text node or is it stored as a JavaScript array like ctrl.array = [1, 2, 3, 4]?

It decides by running through a whole list of known (to AngularJS) input types and, if it does not know about a new, unexpected input type, it decides that it must be stored as a string.

The known types are:
  • text
  • date ('yyyy-MM-dd')
  • datetime-local ('yyyy-MM-ddTHH:mm:ss.sss')
  • time ('HH:mm:ss.sss')
  • week ('yyyy-Www')
  • month ('yyyy-MM')
  • number
  • url
  • email
  • radio
  • checkbox
  • hidden
  • button
  • submit
  • reset
  • file
If you default to being stored as a string, it adds this function to the $formatters object on the caller's instantiated directive.

function stringBasedInputType(ctrl) {
  ctrl.$formatters.push(function(value) {
    return ctrl.$isEmpty(value) ? value : value.toString();
  });
}

As you can see, if the object doesn't have a toString() method, it will call the default toString() method for all JavaScript objects.  If that happens, the user's data may be "stored" as "[object Object]".  Oh, no!

To avoid this, make sure that you empty or change the $formatters array so it is appropriate:

angular
  .directive('input', function(...) {
    return {
      restrict: 'E',
      require: '?ngModel',
      link: function($scope, $element, $attributes, ngModel) {
        ngModel.$formatters = [];
        ...

That way, you can get the unformatted object and handle it appropriately.