1

I am getting started with AngularJS and have a noob problem that I am not sure how to resolve. I am modifying a value outside of angular (I have put it in the .run section only for demonstration purposes), and then attempting to run $apply so that Angular will notice that the scope needs to be updated.

However, in the following code, the {{currentState}} value gets set to "Initial value" and does not ever update to "Second value".

What is the correct approach to get the value to update?

angular.module("exampleApp", [])
.run(function(userNotificationService) {
    userNotificationService.setStatus("Initial value");
    setTimeout(function() {
       userNotificationService.setStatus("Second value");
    }, 1000);
})
.factory('userNotificationService', function($rootScope) {
   var currentState = 'Unknown state'; // this should never be displayed
   return {
     setStatus: function(state) {
        $rootScope.$apply(function() {
            currentState = state;
        });
     },
     getStatus: function() {
        return currentState;
     }
  };
}).directive('currentState', function(userNotificationService) {
    return {
        restrict: 'AE',
        scope: false, // set to false so that directive scope is used for transcluded expressions
        link: function(scope) {
            scope.currentState = userNotificationService.getStatus();
        }
    };
}).controller("defaultCtrl", function ($scope) {
// does nothing
});

And the html is the following:

<body ng-controller="defaultCtrl">
    <div current-state>
        current state: {{ currentState }}
    </div>
</body>

1 Answer 1

2

If your use-case involves a timer, then Angular provides its own timer service called $interval which wraps the call in a scope.$apply for you. You should use that instead of setTimeout.

Now in this case, since you need a one way binding between a service and a value in your scope, you can set up a $watch in your directive:

 .directive('currentState', function(userNotificationService) {
    return {
        restrict: 'AE',
        scope: false, // set to false so that directive scope is used for transcluded expressions
        link: function(scope) {
            scope.$watch(function () { return userNotificationService.getStatus(); }, function (newVal) {
                scope.currentState = userNotificationService.getStatus();
             });
        }
    };

Ideally how you would do it is by creating this one way (or two way) binding in your controller (which you have left empty). The $scope you define on the controller will be available to the directive (if you set $scope: false or $scope: true), and then you can leave the link function empty.

Sign up to request clarification or add additional context in comments.

2 Comments

scope.currentState = newVal works too, as opposed to calling userNotificationService.getStatus() again.
@AlexanderMarquardt It depends. On the first execution, the newVal may be undefined if the first argument to $watch is a string. If the operation is expensive, then it may be better to use newVal and guard against undefined-edness.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.