Wednesday, 3 December 2014

AngularJS ControllerAs and Scope

If we set up a view and controller with controllerAs like below:
// in routing configuration
. . .
.when('/pageOne', {
        templateUrl: 'partials/page-one.html',
        controller: 'CtrlOne',
        controllerAs: 'vm'
})
. . .
// the controller
userApp.controller('CtrlOne', [function () {
    var vm = this;
    vm.nameOne = "test";
    vm.getName = function () {
        return 'aaa';
    };

    this.nameTwo = "blah";
    var nameThree = "ggg"; // not visible from outside
}]);
then we will have these scopes when the page loads:



The first scope is the root scope and the one underneath it is the scope created for the controller. As the picture shows, the variables are created as properties of 'vm' object. If we had used the traditional approach by defining variables on $scope such as $scope.myVariable = ... then these variables would be created on the first level on controller scope (not wrapped inside an object). We can also see when we use controllerAs, other variable that is defined directly not through 'this' object will not be visible from outside.


Adding a Child Controller

Let us add a child controller inside our controller. The child controller:
userApp.controller('ChildCtrl', [function () {
    var vm = this;
    vm.dataOne = 'sss';
    vm.getData = function () {
        return 'bbb';
    }
}]);
And change our view to be like this:
<div>
    Page One
    <div>
        <div>{{parentVm.nameOne}}</div>
        <div ng-controller="ChildCtrl as childVm">
            {{childVm.dataOne}}
            {{parentVm.nameOne}}
        </div>
    </div>
</div>

Now we can see that a new scope is created for the child controller:




Accessing Parent Controller's Variables

On the view, we can simply use '{{parentVm.variableName}}' to refer to a parent controller's variable.

In the child controller script function, accessing parent's variables which controller uses controllerAs is not as easy as if the parent controller had used $scope to register its properties. One way to do this is by using $scope help in the child controller. We need to inject $scope to the controller then refer to the parent controller's instance name to be able to access its variables/properties. Below is an example:
$scope.parentVm.nameOne = 'modify parent var';
//$scope.$parent.parentVm.nameOne = 'modify parent var';    // or could use $parent
The code on the first line is utilising JavaScript prototypal inheritance where it will try to find the property 'parentVm.nameOne' on the current scope then if it is not found, it will try to find on the parent scope and so on until it reaches the root scope. This AngularJS documentation 'Understanding Scope' explains it in more details.
The second code is finding the property on the parent scope. We could append many $parent if required.

If we had tried to do these below inside child controller:
vm.nameOne = 'modified';
$scope.nameOne = 'hhh';
new variables would be created on the current scope. One is on the scope's root level and the other one is inside an object. The picture below shows this:




Creating Local Variables that Hold References

If we want to create a local variable that holds reference to a variable in parent controller then we need to create that variable as an object in parent controller. This is explained as well in the referred AngularJS documentation above. For example:
// create an object variable in parent controller
vm.objVar = { name: 'aaa' };
then in our child controller:
// create the local variable in child controller
vm.childRefVar = $scope.$parent.parentVm.objVar;

// try to do some update
$scope.$parent.parentVm.objVar.name = 'jjj'; // when we update the variable in parent controller, this will reflect in both parent and local variables
vm.childRefVar.name = 'bbb'; // when we modify the local variable, this will reflect in parent variable as well


Best Practices

As we have seen above, accessing variables in parent controller is not straight forward. If you are going to have nested controllers in your view, it is recommended to use a service to share data between the controllers rather than using variables/properties in your controller.

If you do not want to use a service then I think it is better to register the variables or functions that are going to be shared on $scope rather than on 'this' object unless you are going to decide/know the controller's alias object name in advance to be used from codes inside child controller.

1 comment:

Unknown said...

It's no secret that I am a fan of the "Controller As" syntax that was revealed in AngularJS 1.2.0, in fact I wrote an entire post about the Controller As feature. I'm also a fan of readable code and keeping things consistent and simple. We tend to spend a lot of time reading code. In fact, I bet you spend much more time reading code than you might imagine ... more-so than writing it.
More Examples:
AngularJS Certification Training in Chennai