Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the angular scope binding &(ampersand) a one time binding?

Is the angular scope binding &(ampersand) a one time binding? I see it referred to as a one-way binding, but is it also one-time?

Let's say I have:

<my-custom-directive data-item="item" />

And my directive is declared as follows:

.directive('myCustomDirective', [
'$log', function ($log) {
return {
    restrict: 'E',
    templateUrl: '/template.html',
    scope: {
        dataItem: '&'
    }
    controller: function ($scope) {
        // ....
    }
}])

The reason I'm asking if the binding is one-time is because that seems to be what I'm observing, that is. If item in the parent scope is updated, the one in the directive is not updated.

Am I right in saying that the binding is one time?

To achieve what I want, where the directive keeps a copy without affecting the parent scope's item -- I did this:

.directive('myCustomDirective', [
'$log', function ($log) {
return {
    restrict: 'E',
    templateUrl: '/template.html',
    scope: {
        dataItemOriginal: '='
    },
    link: function ($scope) {
        $scope.$watch('dataItemOriginal', function () {
            $scope.dataItem = window.angular.copy($scope.dataItemOriginal);
        });
    },
    controller: function ($scope) {
   //....
   }
}])

Is this proper or is there a better way?

like image 446
Matt Avatar asked Dec 25 '22 20:12

Matt


1 Answers

There is a better way. Your current solution is setting up three watches - two are created by using the "=" binding, and then the extra $watch you create to make a copy. $watches are relatively expensive.

Here is an alternative that doesn't create any watches:

.directive('myCustomDirective', [
'$log', function ($log) {
return {
    restrict: 'E',
    templateUrl: '<div ng-click="clicked()">Click me for current value</div>',
    scope: {
        item: '&'
    },
    controller: function($scope) {
        $scope.clicked = function(){
            alert(item());  //item() returns current value of parent's $scope.item property
        }
        $scope.val = item();  //val is the initial value of $parent.item
        $scope.val = 42; //$parent.item is unaffected. 
    }

}])

& is highly misunderstood. While it can be used for passing functions into an isolated scope, what it actually does is:

provides a way to execute an expression in the context of the parent scope

It generates a function in the directive that, when called, executes the expression in the context of the parent scope. The expression does not have to be a function or function call. It can be any valid angular expression.

So in your example:

<my-custom-directive data-item="item"></my-custom-directive>

scope: {
    item: '&'
}

$scope.item in the directive will be a function that you can call in the controller or in your template. Invoking that function will return the object referenced by "item" in your parent scope. There is no binding with & - no watches are used.

Where the "one-way-binding" misnomer comes in is that using &, the directive cannot change the value of $parent.item, where as when using "=", the directive, by virtue of the $watches created, can. It is also not "one time", either, since it's not technically bound at all.

Since the mechanism that angular uses to generate the function involves $parse, it is possible for the directive to pass in "locals" that override values in the expression with values from the directive. So, in the directive controller, if you did:

item({item: 42})

it would always return 42, regardless of the value of item in the parent scope. It is this feature that makes & useful for executing function expressions on the parent scope with data from the directive.

like image 140
Joe Enzminger Avatar answered Dec 28 '22 07:12

Joe Enzminger