Reputation: 1908
I have a directive that has 2 attributes:
1. a title object passed from the controller.
2. a onchange function passed from the controller.
scope: {
title: '=',
change: '&'
}
//on the link function I update the object, and call the function.
link: function(scope) {
scope.setValue = function() {
scope.title = "Yaniv"
scope.change();
};
},
somehow, eventhough I wrote it the opposite way, it seems that first it calls the function, and only then it updates the title object. What is the best way to overcome this? I already thought about using setTimeout, and it actually worked this thing around. but, I wonder why this issue happened, and whether there is a cleaner solution here. attached JS fiddle: http://jsfiddle.net/sz82r7pg/
Upvotes: 0
Views: 1219
Reputation: 29926
The first thing to note is that the directive has its own isolate scope object, of which the title attribute is bound to the title of the controller scope. This databinding is basically done by watches, and you might know that change detection using watches is not instantaneous, happens inside a digest loop, which always happens after the change was made, and the call stack is emtpied (in the next micro/macro task. I don't know exactly which, but doesn't matter here). Calling a function is however instantaneous. So what happens is the following:
This is totally expected behaviour. The easiest way in your case is not to use your own change detection, but use the one built into angular:
angular.module('zippyModule', [])
.controller("Ctrl3", function ($scope) {
$scope.title = 'Ori';
$scope.$watch("title", function(newValue, oldValue) {
if (newValue !== oldValue) {
alert(newValue);
}
});
})
.directive('zippy', function() {
return {
restrict: 'AE',
scope: {
title:'=zTitle'
},
template: '<button ng-click="setValue()">What would be the value of title??</button>',
link: function(scope) {
scope.setValue = function () {
scope.title = "Yaniv";
};
}
}
});
button {
font-size:24px;
margin:40px;
padding: 10px 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
<div ng-app="zippyModule">
<div ng-controller="Ctrl3">
<div zippy z-title="title"></div>
</div>
</div>
While writing this snippet I realized you are using an ancient version of angularjs in the fiddle (1.0.2). I had to make a little modification to work with 1.5.x.
If you dont want to use watches, then the best you can do is to justt pass the new title to the change function.
angular.module('zippyModule', [])
.controller("Ctrl3", function ($scope) {
$scope.title = 'Ori';
$scope.foo = function(newTitle) {
alert(newTitle);
};
})
.directive('zippy', function() {
return {
restrict: 'AE',
scope: {
change: '&'
},
template: '<button ng-click="setValue()">What would be the value of title??</button>',
link: function(scope) {
scope.setValue = function () {
scope.change({title:"Yaniv"});
};
}
}
});
button {
font-size:24px;
margin:40px;
padding: 10px 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
<div ng-app="zippyModule">
<div ng-controller="Ctrl3">
<div zippy change="foo(title)"></div>
</div>
</div>
UPDATE
It turned out in the comments you wish to make a somewhat tighter coupling between the controller and the directive. Such a thing is usually considered a bad design, but there are times when this is the only right solution (most of the time when non-angular components are involved). So the idea is to create an API object, which can directly be manipulated, and pass it to the directive.
angular.module('zippyModule', [])
.controller("Ctrl3", function ($scope) {
$scope.title = 'Ori';
$scope.fooApiImpl = {
title: "Ori",
change: function() {
alert($scope.fooApiImpl.title);
}
};
})
.directive('zippy', function() {
return {
restrict: 'AE',
scope: {
fooApi: '='
},
template: '<button ng-click="setValue()">What would be the value of title??</button>',
link: function(scope) {
scope.setValue = function () {
scope.fooApi.title = "Yaniv";
scope.fooApi.change();
};
}
}
});
button {
font-size:24px;
margin:40px;
padding: 10px 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.7/angular.js"></script>
<div ng-app="zippyModule">
<div ng-controller="Ctrl3">
<div zippy foo-api="fooApiImpl"></div>
</div>
</div>
Upvotes: 1
Reputation: 222513
title
two-way binding is updated in parent scope at the end of the digest. Until then title
value change is not propagated from directive scope to parent scope.
setValue
is called by ng-click
and runs during the digest. This means that change
should be called after the current digest, with setTimeout
or $imeout
:
scope.setValue = function () {
scope.title = "Yaniv"
$imeout(() => scope.change());
};
Which indicates bad smell and XY problem, because title
update is already tied to digest, and change
is redundant. It's the job for the framework. title
changes should be tracked with
$scope.$watch('title', (newValue, oldValue) => {
if (newValue === oldValue) return;
...
});
in parent scope.
Upvotes: 1