Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Behaviour bindToController in child scope versus isolated scope

I was playing around with the bindToController option for directives. I stumbled upon a seemingly strange difference between the behaviour using a child scope compared to an isolated scope. When I use an isolated scope, an new scope is created for the directive, but changes to the bound controller attributes are forwarded to the parent scope. Yet when I use a child scope instead, my example breaks. (Using bindToController using child scopes should be allowed according to http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html#improvements-in-14 )

The code:

{
    restrict: 'E',
    scope: {},
    controller: 'FooDirCtrl',
    controllerAs: 'vm',
    bindToController: {
        name: '='
    },
    template: '<div><input ng-model="vm.name"></div>'
};

Working demo https://jsfiddle.net/tthtznn2/

The version using a child scope:

{
    restrict: 'E',
    scope: true,
    controller: 'FooDirCtrl',
    controllerAs: 'vm',
    bindToController: {
        name: '='
    },
    template: '<div><input ng-model="vm.name"></div>'
};

Demo: http://jsfiddle.net/ydLd1e00/

The changes to name are forwarded to the child scope, but not to the parent scope. This in contrast to binding to an isolated scope. Why is this?

like image 270
Jan-Willem Gmelig Meyling Avatar asked Oct 19 '22 01:10

Jan-Willem Gmelig Meyling


1 Answers

This is because you are using the same alias for both controllers (and has nothing to do with object vs string value as mentioned in the comments).

As you might know, the controller as alias syntax is merely creating an alias property on scope and sets it to the controller instance. Since you are using vm as the alias in both cases (MainCtrl and FooDirCtrl) you are "shadowing" the MainCtrl alias in the case of the normal child scope. (In this context, "normal" means "prototypally inheriting from parent scope".) Thus, when you are trying to evaluate vm.name (vm for MainCtrl) on the new scope to get the "parent value", it is actually evaluating FooDirCtrl.name (which is undefined) and the same happens when you are trying to assign back to the parent scope.

The isolate scope version is not affected, since the scope is not inheriting from it's parent scope.

Updated fiddle


UPDATE: Taking a closer look at the source code, this might be a bug (because support for bindToController on non-isolate scopes was added "retroactively"). We seem to have been getting away with this bug, because of the prototypal inheritance, but when the names collide we are out of luck.

I have to take a closer look to make sure if it's indeed a bug and if it's "fixable", but for now you can work around it by using different aliases for your controllers (see fiddle above).


That's (part of) why I don't like using vm as my conroller alias (despite it being suggested by popular style-guides) :)

Let's track this in angular.js#13021.

like image 193
gkalpak Avatar answered Nov 11 '22 19:11

gkalpak