Reputation: 2113
I'm trying my hand at creating a directive. Things simply weren't working so I simplified things until I found this basic race condition issue causing problems for me. In my directive's controller I need to do some checks like...
if ($scope.test.someValue === true) { $scope.test.anotherValue += 1; }
Here's my basic directive with some logs to illustrate how this issue manifests.
app.directive('myDirective', function () {
return {
restrict: 'E',
replace: true,
scope: {
test: '='
},
template: '<div><pre>{{ test | json }}</pre></div>',
controller: function ($scope, $timeout) {
// this logs undefined
console.log($scope.test);
// this logs the scope bound 'test' object
$timeout(function() {
console.log($scope.test);
}, 300);
}
};
});
What is the correct way to work with this race condition? I'm worried that in the real world this timeout function is going to work or fail based on how long an api call takes.
Upvotes: 4
Views: 5783
Reputation: 48211
The controller is instantiated during the pre-linking phase (when the scope of the directive is not bound to any parent scope yet).
The controller is where you put the business logic of the directive.
Any initialization code (especially code that depends on the bindings to the parent scope), should be run at the linking phase (i.e. using the link
property of the Directive Definition Object).
app.directive('myDirective', function () {
return {
restrict: 'E',
replace: true,
scope: {
test: '='
},
template: '<div><pre ng-click="someFunc()">{{ test | json }}</pre></div>',
controller: function ($scope) {
/* No two-way bindings yet: test -> undefined */
/* Business logic can go here */
$scope.someFunc = function () {
alert('I implement the business logic !');
};
},
link: function postLink(scope, elem, attrs) {
/* Bindings are in place. Safe to check up on test. */
console.log($scope.test);
}
};
});
Upvotes: 1
Reputation: 907
Remember that at the "link" phase (when you assign your controller), the $scope.test
variable has not been assigned yet - hence the undefined
The $timeout(fn, timeout)
is a way to execute something which will affect something in the $scope. You can set your $timeout() value to 0 and it will still work. The reason for this is because the $timeout(...) function will be deferred till after the current $digest()
cycle.
References: $timeout() $digest()
Additionally, if you want to watch for changes in a particular value you can do:
$scope.$watch("test.somevalue", function(new_val, old_val) {
console.log("somevalue changed!! - increment othervalue");
$scope.othervalue += 1;
});
Upvotes: 4
Reputation: 3280
One way of working with Race conditions is to $watch the variable you want to use, and when its value changes to the value you desire (!= undefined
in your case) you can work with that variable. The expression that needs to be watched gets evaluated every $digest
cycle. Here's an example for your case:
$scope.$watch('test', function(val){
if(val != undefined) {
if ($scope.test.someValue === true) { $scope.test.anotherValue += 1; }
}
})
Upvotes: 0