AngularJS overwrites isolated directive scope

2019-04-08 07:45发布

问题:

Usage:

<my-directive my-var="true"></my-directive>

Directive:

app.directive('myDirective', [
    function () {
        var definition = {
            restrict: "E",
            replace: false,
            transclude: false,
            scope: {
                myVar: '@',
            },
            controller: ['$scope', function($scope) {
                console.log($scope.myVar); // "true"
                $scope.myVar = "false";
                console.log($scope.myVar); // "false"

                setTimeout(function() {
                    console.log($scope.myVar); // "true" (!)
                }, 100);
            }]
        };

        return definition;
    }
]);

Console output

"true"
"false"
"true"

What is exactly happening here? The variable is passed as string ("true"), I'm changing it, then it get's replaced again? I would like to understand the cycle here. Is this because there is some additional compilation or digest cycle kicking in and recalculating all isolated scope values again? I thought that once set, variables passed like this (@, just string in directive) would stay the same?

Is there any way to hook up within directive to a moment, after which string variables are not replaced, or will it always work like this with every digest or whatnot, and I'm forced to use $watch?

回答1:

@ binds the parent scope to the isolated scope and it's a one-way (not one-time) binding. So, any change in the outer scope will reset the value in the directive's isolate scope.

Under the covers, Angular calls attrs.$observe on the element attribute with "@". What this does is that it queues an observer function for the next $digest cycle after the compilation. That observer function is what sets the scope variable every time there is a change in the interpolated value assigned to the attribute.

So, the high-level steps are:

1) for `@` scope variable, set an observer function and queue it for later
2) set `@` scope variable to the interpolated value
3) run the controller, pre- and post-link functions
...
N) observer runs against interpolated value and sets the scope value

So, given this, you can now see why your change didn't persist. It would, if you made it after all of these steps - i.e. with a timeout or in response to an event. And only if the attribute interpolated value hasn't changed.

This is NOT so with "=".

Here's a plunker illustrating the differences.

If you want a one-time passage of variables, just set the scope variable using an attribute in the link function or the controller

scope.myVar = $attrs["my-var"];

and remove myVar from the scope.