1

I am fetching data from websocket in a factory. Every time a message arrives I update it.

In my controller I have bound the same inside the $scope. However when the websocket message comes, UI doesn't get updated.

After doing a lot of search I figured out that I need to call $scope.$apply if I need to update the UI (if changes are not saved by using any angular method).

I set up a timer using setInterval to call $scope.$apply every second. But it is a hack. I would like to remove this. Is there a way to tell angular to refresh from inside the factory, without using this hack?

Factory:

app.factory('StocksFactory', function() {
    ...
    factory.stocksCurrentPrices = {};

    // Subscribe to the websocket for stock prices
    const stocksSocket = new WebSocket('ws://...');
    stocksSocket.onmessage = function(event) {
        // Adding to current prices
        factory.stocksCurrentPrices[..] = ...;
    };

    return factory;
});

Controller:

app.controller('StocksController', function($scope, StocksFactory) {

    $scope.stocks = StocksFactory.stocksCurrentPrices;

    // HACK This is used to force the digest every second as the changes in $scope.stocks
    // was not reflected in the UI
    setInterval(function() {
        $scope.$apply();
    }, 1000);
});
Ishan
  • 3,303
  • 5
  • 29
  • 47

3 Answers3

2

You can not use $scope in an AngularJS service, since services do not belong to any scope, but they are singlenton through the application.

But you CAN inject the $rootScope into the service and notify through the entire application, like this:

app.factory('StocksFactory', function($rootScope, $timeout) {
    ...
    factory.stocksCurrentPrices = {};

    // Subscribe to the websocket for stock prices
    const stocksSocket = new WebSocket('ws://...');
    stocksSocket.onmessage = function(event) {
        // Adding to current prices
        factory.stocksCurrentPrices[..] = ...;

        // notify of changes
        $timeout(function() {
            $rootScope.$apply();
        })
    };

    return factory;
});

Notice that:

lealceldeiro
  • 14,342
  • 6
  • 49
  • 80
  • Updating the whole `$rootScope` with `$apply()` is a bad idea, performance issues. – Fernando Carvajal Jan 25 '18 at 21:31
  • @FernandoCarvajal, that is why I linked some other options that can be used. However, it dependents on the specific OP's scenario which one is more convenient to be used. But I agree with you, totally, if it is not used properly it can lead to performance issues. – lealceldeiro Jan 25 '18 at 21:37
1

You can inject $rootScope in a factory and use $broacast in there to emit that it has changed.

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
0

Add $rootScope in your factory and broadcast a message when the stockmarket has change.

app.factory('StocksFactory', ['$rootScope', function($rootScope) {
    var factory = {}
    factory.stocksCurrentPrices = {};

    const stocksSocket = new WebSocket('ws://...');
    stocksSocket.onmessage = function(event) {
        factory.stocksCurrentPrices[..] = ...;
        $rootScope.$broadcast('stocks-just-change')
    };

    return factory;
}])

Then in your controller just listen for that broadcast message and update your $scope.stocks

app.controller('StocksController', function($scope, StocksFactory) {
    $scope.stocks = StocksFactory.stocksCurrentPrices;

    $scope.$on('stocks-just-change', function(){
        $scope.stocks = StocksFactory.stocksCurrentPrices;
        //$scope.$apply() //use this if you'll receive updates very very fast
    }
})
Fernando Carvajal
  • 1,869
  • 20
  • 19