Thursday, September 18, 2014

$scope or this?

Why is it that the Angular JS docs use $scope in controllers and not this?

It's because they're not the same thing.  The scope that is injected into the controller is prepared before the controller is created.

During the compilation phase, DOM elements, nodes, with directives are found.  Then different types of linking functions are created for each of the nodes based on if the nodes have children or not.

During the linking phase, Angular loops through all of the nodes and links them, pairs them up with a scope.  The `nodeLinkFn` then creates the scope, either brand new or inherited from an outer scope.

childScope = scope.$new();

jqLite attaches this new scope to the node.

jqLite.data(node, '$scope', childScope);

Then, based on the type of linking function that was created, the scope is then tied to the controller.

// locals is a bunch of attributes for the controller, including $scope
controllerInstance = $controller(controller, locals);


$controller is a service that is used to look up the controller we're asking for.  The controller definitions were already created, this just looks up the injected stuff and the constructor function.  Then the injection happens.

instance = $injector.instantiate(expression, locals);

During the instantiation, the constructor function's prototype, the one we wrote, is assigned to the new `Constructor` object's prototype.  Then a new `Constructor` instance is created.  This is all really just standard JS inheritance footwork.  The next part is what we've been waiting for.

`invoke` is called, which actually injects the dependencies into our shiny new object.  This is how the '$scope' dependency we referenced when writing our controller gets set up to be the `$scope` argument within our function.  Finally, our function is called with the new polished `Constructor` instance as its scope.

// fn is the function we injected
// self is the new Constructor object
// args are all the prepared dependencies
return fn.apply(self, args);


So, we're finally inside the controller from our perspective.  Now it should make some sense.

app.controller('FooCtrl', ['$scope', function ($scope) {
    // `this` is the new Constructor object.
    // `$scope` is the scope our controller is tied to the directive with.

    this.message = 'I will not be available to directives.';
    $scope.message = 'I will be.';
}]);

This highlights something, though.  If `$scope` is the context in which the directive is tied to the controller, isn't `this === $scope` within methods of the controller?  Also, `this` is used for inheritance, `$scope` is used for communicating with the directive, what about this gap that local variables live in?

app.controller('FooCtrl', ['$scope', function ($scope) {

    // Local variables can hide out here safely.
    var privateMessage = 'I am available through a closure.';

    // This is pointless.  We're adding `message` to the Constructor object.  Pretty dumb.
    this.message = 'I will not be available to directives.';

    // Being well-behaved, we use $scope like it taught us to in the docs.
    $scope.message = 'I will be available.';

    // Controller methods are called within the context $scope.
    $scope.doClick = function () {
     
        // You could use `this`, but it may lead to confusion.  With all the trickiness,
        // wouldn't it be better to be explicit?
        // $scope === this
        this.message = privateMessage;
    };
}]);

OK, I feel better.  We're going to keep using $scope, but now we'll know why.

No comments:

Post a Comment