12

Let me start by saying that what I am trying to do is probably not considered good practice. However, I need to do something like this in order to migrate a large web app to AngularJs in small incremental steps.

I tried doing

 $scope.$watch(function () { return myVar; }, function (n, old) {
                    alert(n + ' ' + old);
                });

Where myVar is a global variable (defined on window)

And then changing myVar from the console. But it only fires when first setting up the watcher.

It works if I update myVar from within the controller (see http://jsfiddle.net/rasmusvhansen/vsDXz/3/, but not if it is updated from some legacy javascript

Is there any way to achieve this?

Update I like Anders' answer if the legacy code is completely off limits. However, at the moment I am looking at this approach which seems to work and does not include a timer firing every second:

// In legacy code when changing stuff    
$('.angular-component').each(function () {
        $(this).scope().$broadcast('changed');
});

// In angular
$scope.$on('changed', function () {
    $scope.reactToChange();
});

I am awarding points to Anders even though I will go with another solution, since his solution correctly solves the problem stated.

Dan Atkinson
  • 11,391
  • 14
  • 81
  • 114
rasmusvhansen
  • 1,452
  • 1
  • 12
  • 13
  • likely don't want to use `each` for the broadcast so angular only does one digest – charlietfl Mar 09 '13 at 12:55
  • 1
    In your sample code, what did `.angular-component` point to? – mlhDev Nov 11 '14 at 19:40
  • In my case, I selected the element I actually wanted to change (using `angular.element(document.getElementById())` because I wasn't using jQuery), then called `scope().$broadcast('customEventName');`. Note that I had to use `$apply()` inside the `$on('customEventName')` function to get the watchers to be called. – Guy Schalnat May 24 '17 at 17:45

1 Answers1

13

The issue here is probably that you're modifying myVar from outside of the Angular world. Angular doesn't run digest cycles/dirty checks all the time, only when things happen in an application that should trigger a digest, such as DOM events that Angular knows about. So even if myVar has changed, Angular sees no reason to start a new digest cycle, since nothing has happened (at least that Angular knows about).

So in order to fire your watch, you need to force Angular to run a digest when you change myVar. But that would be a bit cumbersome, I think you would be better of to create a global observable object, something like this:

<!doctype html>
<html ng-app="myApp">
<head>
    <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
    <script src="http://code.angularjs.org/1.0.5/angular.min.js"></script>
    <script>
    // Outside of Angular
    window.myVar = {prop: "value1"};
    var myVarWatch = (function() {
        var watches = {};

        return {
            watch: function(callback) {
                var id = Math.random().toString();
                watches[id] = callback;

                // Return a function that removes the listener
                return function() {
                    watches[id] = null;
                    delete watches[id];
                }
            },
            trigger: function() {
                for (var k in watches) {
                    watches[k](window.myVar);
                }
            }
        }
    })();

    setTimeout(function() {
        window.myVar.prop = "new value";
        myVarWatch.trigger();
    }, 1000);

    // Inside of Angular
    angular.module('myApp', []).controller('Ctrl', function($scope) {
        var unbind = myVarWatch.watch(function(newVal) {
            console.log("the value changed!", newVal);
        });

        // Unbind the listener when the scope is destroyed
        $scope.$on('$destroy', unbind);
    });
    </script>
</head>
<body ng-controller="Ctrl">

</body>
</html>
Anders Ekdahl
  • 22,685
  • 4
  • 70
  • 59
  • 1
    Thanks for the input Anders, I think this is a very usable way of doing it if there is no way of changing the legacy code. I am able to make small changes to the legacy code, so I think I am going with a different approach (see my update) – rasmusvhansen Mar 08 '13 at 13:58
  • 1
    Btw, check out `Object.observe()`. It allows you to watch for changes on an object. It's still not implemented in a lot of browsers, but there are polyfills for it. – Anders Ekdahl Mar 10 '13 at 09:11
  • I needed to use $scope.$apply inside of the angular myVarWatch.watch to have angular digest the changes. – willJk Jan 13 '15 at 15:24