Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Isolate scope binding to parent function inconsistent context #8552

Closed
pocesar opened this issue Aug 10, 2014 · 9 comments
Closed

Isolate scope binding to parent function inconsistent context #8552

pocesar opened this issue Aug 10, 2014 · 9 comments

Comments

@pocesar
Copy link
Contributor

pocesar commented Aug 10, 2014

When using an isolated scope, I find myself having to return a bound function from a & isolated scope binding, or the this becomes undefined or Window DOM.

    <div ng-controller="MyCtrl as mycontroller">
      <div some-directive="mycontroller.func"></div>
      <div ng-click="mycontroller.func()">Regular ngClick</div>
    </div>
.controller('MyCtrl', function(){
   this.func = function(){ console.log(this); };
})
.directive('someDirective', function(){
  return {
    restrict: 'A',
    scope: { 'fn': '&someDirective' }
    link: function(scope){
       scope.doh = function(){
         scope.fn()();
       };
    }    
  };
});

http://plnkr.co/edit/Dnhd67JxAnbcG9i21b3a?p=preview

@caitp
Copy link
Contributor

caitp commented Aug 10, 2014

You're using the & binding weirdly here, this would be more appropriate: http://plnkr.co/edit/RBsjbMOSBEdTJMJE4iiF?p=preview

& means that the attribute is an expression, which you evaluate with scopeName({ optionalLocalVariables }), which will in turn evaluate the expression on the scope from which the isolate scope came.

If you use this correctly, you'll find that the this binding works as expected.

@pocesar
Copy link
Contributor Author

pocesar commented Aug 10, 2014

And if I need to trigger the bound function not from ng-click, but from inside the link function/controller? Your plunkr is not the same in functionality as mine.

@caitp
Copy link
Contributor

caitp commented Aug 10, 2014

see the previous revision, which would be the same functionality (just $scope.bindingName({ optionalLocals }))

@pocesar
Copy link
Contributor Author

pocesar commented Aug 10, 2014

but then I can't pass arguments to the function, which defeats the purpose to do it programatically. the only way I found so far is to return a closure bound to the object:

$scope.someInstanciatedClass = ...;
$scope.closure = function(){
  return function(){
     $scope.someInstanciatedClass.method(argumentsSomewhere);
  };
};
<div some-directive="closure()"></div>

that's a lot of code if I could just use some-directive="someInstanciatedClass.method" and it would just retain the context of someInstanciatedClass

@caitp
Copy link
Contributor

caitp commented Aug 10, 2014

you can pass any arguments you want to the function, using the locals object, which I demonstrated above! The expression gets evaluated within the context of the parent scope, so you can pass any name bound in the parent scope --- in addition to any bindings that you provide via the locals object.

For instance, if I want to pass "caitp" to the callback, I could say $scope.fn({ name: "caitp" }) --- then, in the expression, name will be bound to "caitp" (and thus, if the expression is select(name), the end result would be select("caitp").

This whole issue seems to be a misunderstsanding about how these expression bindings work and what they're used for. It's not like C, you aren't passing the address of a function, and you don't need to care about it's signature. What you're actually doing is passing an expression which is evaluated within the context of the parent scope, and if that expression calls a function, then it calls a function.

You can add local name bindings to that expression as I've shown, and this is used in the library to pass $event to ng- directives, so there are examples in core of how this works.

But yeah, it's not "pass a callback function", you can't expect it to behave as a callback function. When evaluating a function in an expression, yes it is bound to the context that we find it in --- if any --- but if you're evaluating the function yourself, then it's bound to whatever context you personally bind it to, and you have no way of knowing about where it ought to be bound to, so don't try.

I hope that explains things a bit for you, it's pretty simple stuff.

@pocesar
Copy link
Contributor Author

pocesar commented Aug 10, 2014

nope, this doesnt work, as I'm trying to explain:

angular.module('stuff', []).controller('MyCtrl', function(){
   this.func = function(data){ console.log(data, this); };
})
.directive('someDirective', function(){
  return {
    restrict: 'A',
    scope: { 'fn': '&someDirective' },
    template: '<div ng-click="doh()">Clicky</div>',
    link: function(scope){
       scope.doh = function(){
         scope.fn({data: true});
       };
    }    
  };
});

@caitp
Copy link
Contributor

caitp commented Aug 10, 2014

that does actually work. <div some-directive="someInstanciatedClass.func(data)"></div> will pass true to func's first parameter (because you have a local name data bound to the value true), because as I explained, &-bindings are not callback functions, they're expressions that you evaluate in the parent context, with the option of passing in your own custom name bindings. The function invoked in the expression can use those name bindings, or ignore them if it choses to.

@pocesar
Copy link
Contributor Author

pocesar commented Aug 10, 2014

so, in the end, just passing a closure can keep the context programatically, is the solution I've been using for a while for the context problem.
although I still think the context thorough events and directives be more consistent.

@caitp
Copy link
Contributor

caitp commented Aug 10, 2014

it's just that you're looking at it the completely wrong way. When the parser calls a function, it will use either the object which contains the function (IE, the immediate parent context of the function), or the scope itself.

When YOU invoke the passed function, you are responsible for calling it with the appropriate context. Angular can't make this magic happen for you, it doesn't work that way, that's just the laws of javascript.

The solution is: stop thinking of & as a way to pass callback functions, you are not passing callback functions. You are passing an expression, which is evaluated within the parent execution context, and which optionally has local name bindings (like $event, for instance).

This makes perfect sense, and it will work for your use case, but only if you stop imagining that & means "callback", because it doesn't. It's not a callback, it's an expression. You aren't calling a function, you're evaluating an expression that calls a function. Maybe the result of that expression is a function, but if you want to call that function, you need to attach it to a context yourself. You shouldn't use it this way, though --- that would be an unfortunate thing to do

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants