4

I'm trying to get data coming from a websocket to automatically update value inside a controller's scope.

My Service:

mimosaApp.service("Device", function ($websocket) {
    var self = this;
    var ws = $websocket.$new({
        url: "ws://" + window.location.host + ":81",
        //mock: true,
        reconnect: true
    });
    this.data = {};

    ws.$on("$open", function() {
        ws.$emit("get", "device");
    });
    ws.$on("$message", function (message) {
        console.log("WS Received", message);
        for(var key in message) {
            self.data[key] = message[key];
        }
        console.log(self.data); // At this point, self.data contains the proper data.
    });
    this.send = function (obj) {
        ws.$emit("set", obj);
    };
});

And my simple controller:

angular.module("MimosaApp").controller("PageController", ["Device", "$scope", "$http", function(Device, $scope, $http) {
    $scope.device = Device;
}]);

When a socket connection is made, the browser sends a message asking for data (the $open event). When it gets a response, it updates the Device.data object with the JSON object.

But I'm not seeing this reflect inside my controller scope/view. If inside the Controller, I set something like Device.data.name ='blah'; I can see the name property in the controller scope/view.

I'm a little new to Angular so sorry if my question doesn't quite make sense. :)

My view is trying to use it like:

<div class="container-fluid">
    location
    <ul>
        <li ng-repeat="(key, value) in device.data">
            {{key}}: {{ value }}
        </li>
    </ul>
    <p>{{device.data.face}}</p>
</div>
Charlie
  • 1,646
  • 5
  • 22
  • 40

2 Answers2

3

Looking at the source it does not seem to invoke digest cycle using scope.$apply within the $on handler. Which means that angular does not know about any update to its view bindings, so no change gets reflected in the view. So you would need to do it manually in your service, you could either inject a $rootScope or just a $timeout to trigger a digest cycle.

Example:-

inject $timeout

 ws.$on("$message", function (message) {
        console.log("WS Received", message);
        for(var key in message) {
            self.data[key] = message[key];
        }
        console.log(self.data); // At this point, self.data contains the proper data.
       $timeout(angular.noop); //<-- just invoke a dummy digest
    });

inject $rootScope

 ws.$on("$message", function (message) {
        console.log("WS Received", message);
        for(var key in message) {
            self.data[key] = message[key];
        }
        console.log(self.data); // At this point, self.data contains the proper data.
      $rootScope.$apply(); //invoke digest
    });

Or even create a dummy promise in your service using $q.

mimosaApp.service("Device", function ($websocket, $q) {
    var self = this;
    var _dummyPromise = $q.when(); //<-- here
    var ws = $websocket.$new({
        url: "ws://" + window.location.host + ":81",
        //mock: true,
        reconnect: true
    });
    //...


    ws.$on("$message", function (message) {
        console.log("WS Received", message);
        for(var key in message) {
            self.data[key] = message[key];
        }
       _dummyPromise.then(angular.noop); //<-- here
    });
    this.send = function (obj) {
        ws.$emit("set", obj);
    };
});
PSL
  • 123,204
  • 21
  • 253
  • 243
  • I would accept yours PSL but Hank answered a minute before hand up above. :/ Both work though! – Charlie May 19 '15 at 23:48
  • @Charlie Thats alright, i wanted to write a thorough answer, which takes time.. However i would not suggest injecting rootScope in a service though that is why i wanted to provide other alternatives as well. Never knew just the time is what matters, lol!! I went and looked up the source without making any assumptions, in the other answer there is a line `if(!$rootScope.$$phase) ` which came out of assumption, you wont see it in mine because i know on that call stack no digest cycle is invoked.. – PSL May 19 '15 at 23:49
  • Hrm... let me try this out. Maybe I shouldn't do the rootscope. :D – Charlie May 19 '15 at 23:52
  • The $timeout works fine too. And guess its more Kosher since I don't mess with the rootScope. – Charlie May 19 '15 at 23:54
  • @Charlie exactly.. just try to avoid rootscope as much as possible.. Infact `$rootScope.$apply()/$digest()` should never be in this code it should be at the top of the call stack (i.e in the library where it is invoked on [this line](https://github.com/wilk/ng-websocket/blob/master/ng-websocket.js#L117)). – PSL May 19 '15 at 23:56
2

The most likely cause is that the $on callback isn't triggering a $digest cycle to inform the rest of the app of any change.

You can do that manually by injecting $rootScope

mimosaApp.service("Device", function ($rootScope, $websocket)

and then triggering a $digest after updating your data

ws.$on("$message", function (message) {
    console.log("WS Received", message);
    for(var key in message) {
        self.data[key] = message[key];
    }
    if(!$rootScope.$$phase) { // prevents triggering a $digest if there's already one in progress
        $rootScope.$digest()
    }
    console.log(self.data); // At this point, self.data contains the proper data.
});
HankScorpio
  • 3,612
  • 15
  • 27
  • Perfect. Out of curiosity, how would someone know about this without asking? Meaning... this is the first I've heard of the rootScope while doing all the angular tutorials. Wondering if I'm missing something or just need more angular experience. – Charlie May 19 '15 at 23:48
  • Just experience. It's an essential part of angular that powers its magic. $scopes are hierarchical, so there's always a parent, and the one at the very top is called... $rootScope ( naturally :P ). It can be done using $scope too, but services are meant to be standalone so they generally don't have a $scope of their own. It's stuff new users don't really need to know, and is understandably too confusing for most beginners. – HankScorpio May 19 '15 at 23:53