Short answer: to get the expected value foo in your link function you have to observe the attribute:
attr.$observer('myDirectiveFoo', function(value){
console.log(value);
console.log($scope.foo);
});
both console outputs will give you the expected bar value. Why? the attribute value must be interpolated and this will happen after the link function was called.
Long answer: The documentation stated for the attr object that is passed to the link function:
Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.
Let's have a look at a modified version of you example. It shows also the differences between = und @ scoping.
This is our html:
<body ng-controller="MainCtrl">
<span my-directive my-directive-foo="hello {{name}}" my-directive-bar="name" ></span>
</body>
This is the controller:
app.controller('MainCtrl', function($scope, $timeout, $interpolate){
$scope.name = 'roy';
$timeout(function(){
$scope.name = 'michael';
},4000);
});
As you can see, we have a name property that will change from roy to michael after 4 seconds.
This is the directive:
app.directive('myDirective', function() {
return {
scope: {
foo: '@myDirectiveFoo',
bar: '=myDirectiveBar'
},
link: function ($scope, iElement, iAttrs, controller) {
console.log('link foo: '+$scope.foo);
console.log('link bar: '+$scope.bar);
iAttrs.$observe('myDirectiveFoo',function(value){
console.log('link observed foo: '+$scope.foo, value);
});
$scope.$watch('bar', function(newValue, oldValue){
console.log('watch', oldValue, newValue);
});
console.log('link done');
},
controller: function($scope){
console.log('controller foo:'+$scope.foo);
console.log('controller bar:'+$scope.bar);
}
};
});
We have two isolated scope properties. foo configured as one-way-binding and bar configured as two-way-binding. In the controller output we will see, that the two-way-binding ($scope.bar) is available immediately and the one-way-binding ($scope.foo) is not. In the link function we have the same result. If the linking is done we will see that the observer and the watcher will fire with the current (and expected) values. The observer and the watcher will fire again if the name property changed their value after 4 seconds.
see it live @ PLUNKR