Constellates
Constellates

Reputation: 2113

How can I overcome race conditions within directives without a timeout function?

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

Answers (3)

gkalpak
gkalpak

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

sabhiram
sabhiram

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

Yogesh Mangaj
Yogesh Mangaj

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

Related Questions