将 AngularJS 作用域变量从指令传递到控制器的最简单方法?

将 AngularJS 作用域变量从指令传递到控制器最简单的方法是什么?我看到的所有例子看起来都很复杂,有没有一种方法可以从指令访问控制器,并设置其中一个范围变量?

192031 次浏览

Edited on 2014/8/25: Here was where I forked it.

Thanks @anvarik.

Here is the JSFiddle. I forgot where I forked this. But this is a good example showing you the difference between = and @

<div ng-controller="MyCtrl">
<h2>Parent Scope</h2>
<input ng-model="foo"> <i>// Update to see how parent scope interacts with component scope</i>
<br><br>
<!-- attribute-foo binds to a DOM attribute which is always
a string. That is why we are wrapping it in curly braces so
that it can be interpolated. -->
<my-component attribute-foo="\{\{foo}}" binding-foo="foo"
isolated-expression-foo="updateFoo(newFoo)" >
<h2>Attribute</h2>
<div>
<strong>get:</strong> \{\{isolatedAttributeFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedAttributeFoo">
<i>// This does not update the parent scope.</i>
</div>
<h2>Binding</h2>
<div>
<strong>get:</strong> \{\{isolatedBindingFoo}}
</div>
<div>
<strong>set:</strong> <input ng-model="isolatedBindingFoo">
<i>// This does update the parent scope.</i>
</div>
<h2>Expression</h2>
<div>
<input ng-model="isolatedFoo">
<button class="btn" ng-click="isolatedExpressionFoo({newFoo:isolatedFoo})">Submit</button>
<i>// And this calls a function on the parent scope.</i>
</div>
</my-component>
</div>
var myModule = angular.module('myModule', [])
.directive('myComponent', function () {
return {
restrict:'E',
scope:{
/* NOTE: Normally I would set my attributes and bindings
to be the same name but I wanted to delineate between
parent and isolated scope. */
isolatedAttributeFoo:'@attributeFoo',
isolatedBindingFoo:'=bindingFoo',
isolatedExpressionFoo:'&'
}
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
$scope.foo = 'Hello!';
$scope.updateFoo = function (newFoo) {
$scope.foo = newFoo;
}
}]);

Wait until angular has evaluated the variable

I had a lot of fiddling around with this, and couldn't get it to work even with the variable defined with "=" in the scope. Here's three solutions depending on your situation.


Solution #1


I found that the variable was not evaluated by angular yet when it was passed to the directive. This means that you can access it and use it in the template, but not inside the link or app controller function unless we wait for it to be evaluated.

If your variable is changing, or is fetched through a request, you should use $observe or $watch:

app.directive('yourDirective', function () {
return {
restrict: 'A',
// NB: no isolated scope!!
link: function (scope, element, attrs) {
// observe changes in attribute - could also be scope.$watch
attrs.$observe('yourDirective', function (value) {
if (value) {
console.log(value);
// pass value to app controller
scope.variable = value;
}
});
},
// the variable is available in directive controller,
// and can be fetched as done in link function
controller: ['$scope', '$element', '$attrs',
function ($scope, $element, $attrs) {
// observe changes in attribute - could also be scope.$watch
$attrs.$observe('yourDirective', function (value) {
if (value) {
console.log(value);
// pass value to app controller
$scope.variable = value;
}
});
}
]
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
// variable passed to app controller
$scope.$watch('variable', function (value) {
if (value) {
console.log(value);
}
});
}]);

And here's the html (remember the brackets!):

<div ng-controller="MyCtrl">
<div your-directive="\{\{ someObject.someVariable }}"></div>
<!-- use ng-bind in stead of \{\{ }}, when you can to avoids FOUC -->
<div ng-bind="variable"></div>
</div>

Note that you should not set the variable to "=" in the scope, if you are using the $observe function. Also, I found that it passes objects as strings, so if you're passing objects use solution #2 or scope.$watch(attrs.yourDirective, fn) (, or #3 if your variable is not changing).


Solution #2


If your variable is created in e.g. another controller, but just need to wait until angular has evaluated it before sending it to the app controller, we can use $timeout to wait until the $apply has run. Also we need to use $emit to send it to the parent scope app controller (due to the isolated scope in the directive):

app.directive('yourDirective', ['$timeout', function ($timeout) {
return {
restrict: 'A',
// NB: isolated scope!!
scope: {
yourDirective: '='
},
link: function (scope, element, attrs) {
// wait until after $apply
$timeout(function(){
console.log(scope.yourDirective);
// use scope.$emit to pass it to controller
scope.$emit('notification', scope.yourDirective);
});
},
// the variable is available in directive controller,
// and can be fetched as done in link function
controller: [ '$scope', function ($scope) {
// wait until after $apply
$timeout(function(){
console.log($scope.yourDirective);
// use $scope.$emit to pass it to controller
$scope.$emit('notification', scope.yourDirective);
});
}]
};
}])
.controller('MyCtrl', ['$scope', function ($scope) {
// variable passed to app controller
$scope.$on('notification', function (evt, value) {
console.log(value);
$scope.variable = value;
});
}]);

And here's the html (no brackets!):

<div ng-controller="MyCtrl">
<div your-directive="someObject.someVariable"></div>
<!-- use ng-bind in stead of \{\{ }}, when you can to avoids FOUC -->
<div ng-bind="variable"></div>
</div>

Solution #3


If your variable is not changing and you need to evaluate it in your directive, you can use the $eval function:

app.directive('yourDirective', function () {
return {
restrict: 'A',
// NB: no isolated scope!!
link: function (scope, element, attrs) {
// executes the expression on the current scope returning the result
// and adds it to the scope
scope.variable = scope.$eval(attrs.yourDirective);
console.log(scope.variable);


},
// the variable is available in directive controller,
// and can be fetched as done in link function
controller: ['$scope', '$element', '$attrs',
function ($scope, $element, $attrs) {
// executes the expression on the current scope returning the result
// and adds it to the scope
scope.variable = scope.$eval($attrs.yourDirective);
console.log($scope.variable);
}
]
};
})
.controller('MyCtrl', ['$scope', function ($scope) {
// variable passed to app controller
$scope.$watch('variable', function (value) {
if (value) {
console.log(value);
}
});
}]);

And here's the html (remember the brackets!):

<div ng-controller="MyCtrl">
<div your-directive="\{\{ someObject.someVariable }}"></div>
<!-- use ng-bind instead of \{\{ }}, when you can to avoids FOUC -->
<div ng-bind="variable"></div>
</div>

Also, have a look at this answer: https://stackoverflow.com/a/12372494/1008519

Reference for FOUC (flash of unstyled content) issue: http://deansofer.com/posts/view/14/AngularJs-Tips-and-Tricks-UPDATED

For the interested: here's an article on the angular life cycle