This plunkr demonstrates a few different scenarios and capabilities when moving data between a model and a directive. Like other places in Angular JS there are quite a number of different approaches that work, which grants a lot of flexibility but also leads to some confusion.
The example uses a simple controller.
module.controller("TestController", function($scope){ $scope.magicValue = 1; $scope.increment = function(){ $scope.magicValue += 1; }; });
The markup for the sample displays the controller’s magicValue, and provides a button to change the value. There are also 5 versions of a valueDisplay directive.
<div ng-controller="TestController"> Controller: {{magicValue}} <button ng-click="increment()">Click</button> <ol> <li><value-display1 value="magicValue"></value-display1></li> <li><value-display2 value="magicValue"></value-display2></li> <li><value-display3 value="{{magicValue}}"></value-display3></li> <li><value-display4 value="magicValue"></value-display4></li> <li><value-display5 value="{{magicValue}}"></value-display5></li> </ol> </div>
The first version of valueDisplay will put the value on the screen, but will never update the screen if the magic value changes. This approach is only reasonable for static data since the directive reads the value directly from the controller’s scope (using the name specified in the attribute), places the value in the element, then never looks back.
module.directive("valueDisplay1", function () { return { restrict: "E", link: function (scope, element, attrs) { element.text(scope[attrs.value]); } }; });
The second version of valueDisplay uses a $watch to update the screen as the model value changes. Notice the attribute value (value="magicString") is still working like an expression to retrieve and watch data directly from the controller’s scope.
module.directive("valueDisplay2", function () { return { restrict: "E", link: function (scope, element, attrs) { scope.$watch(attrs.value, function(newValue) { element.text(newValue); }); } }; });
The third version is a slight variation that will read and observe data in the value attribute itself instead of going to the controller. There are a few differences to note. First, the directive will always retrieve the value as a string. Secondly, the view has to use interpolation to place the proper value into the attribute (i.e. value="{{magicValue}}"). Finally, $observe is used instead of a scope watch since the directive wants to watch changes on the attribute, not the controller scope.
module.directive("valueDisplay3", function () { return { restrict: "E", link: function (scope, element, attrs) { attrs.$observe("value", function (newValue) { element.text(newValue); }); } }; });
The fourth version moves to using an isolate scope to detach itself from the parent scope. Two-way data binding is setup between the directive's isloated scope and the parent model using using "=", so the framework will take care of pushing and pulling data between the directive’s scope and the parent scope. The view still chooses the model property to use with the value attribute in the markup (value="magicValue"), and displays the value using a template (which will automatically watch for changes).
module.directive("valueDisplay4", function () { return { restrict: "E", scope: { value: "=" }, template: '{{value}}', }; });
The final version of valueDisplay uses attribute binding ("@"), which also works in both directions and will update the view as the model value changes (and vice-versa, if we had an input).
module.directive("valueDisplay5", function () { return { restrict: "E", scope: { value: "@" }, template: '{{value}}', }; });
In the end, there are even more options available (we didn’t even look at executing expressions to move data), but that can be a topic for a future post. I currently tend to use option 4 for binding complex objects from the model to a directive, and option 5 for moving simple customization values like headings, labels, and titles.