JLavoie
JLavoie

Reputation: 17626

How to update views with an asynchronous events in a service or factory in Angular?

I have a service that receives asynchronous events (from socket.io) and I want to update the views when a new event is triggered. As the view is not binded to the service, I can't update the view. What is the best way to achieve that?

Upvotes: 1

Views: 2890

Answers (2)

JLavoie
JLavoie

Reputation: 17626

I found 5 ways to achieve that:

Option 1: From the controller, pass the $rootScope in the service or factory. Then when the event is triggered, use the function $rootScope.apply(). This will update the view. For instance, in your controller:

angular.module('Project', ['Messages']).controller('MyCtrl', function(Messages){
    // here we pass the $rootScope (or $scope if you want) to the service
    MyService.init($rootScope); 
}

angular.module('Project.factory', []).factory('Messages', function(){
    var Messages = {};
    var scope;
    Messages.init = function(scope_ctrl){ 
        scope = scope_ctrl;             
    }
    return Messages;
}

In your asynchronous function being fired within your service/factory, call scope.apply():

socket.on('update', function(data) {
    scope.apply();
})

Option 2: Use a callback. Define a callback function in the controller:

  angular.module('Project', ['Messages']).controller('MyCtrl', function(Messages){

    // Call function
    var callback = function(){
       $scope.$apply()
    }
    // here we pass the $rootScope (or $scope if you want) to the service
    MyService.init(callback); 
}

angular.module('Project.factory', []).factory('Messages', function(){
    var Messages = {};
    var callback;
    Messages.init = function(callback_ctrl){ 
        callback = callback_ctrl;             
    }
    return Messages;
}

In your asynchronous function being fired within your service/factory, call the function callback, which will then execute the code as defined in the controller:

socket.on('update', function(data) {
    callback(); 
})

Option 3: In your service, update the element Id in the view.

socket.on('update', function(data) {
    var element = angular.element($('#MyElementWithController'));
    element.scope().$apply();
})

I personnaly don't like this option because you need to manage the element from your service, so it's not good for code re-usability.


Option 4: Use $q (the best approach) $q is wired up with the Angular digest cycle so no need to do anything with $apply().

In your asynchronous function being fired withing your service/factory, use $q like this:

socket.on('update', function(data) {
    // Function being fired on the asyncrhonous event
    return doSomething();
})

Where:

function doSomething(){
    var deferred = $q.defer();
    deferred.resolve();
    return deferred.promise;
}

** Do not forget to include the parameter $q in your service. Like:

angular.module('Project.factory', []).factory('Messages', function($q)
{

}

Option 5: Use broadcast:

angular.module('Project.factory', []).factory('Messages', function($broadcast){
    socket.on('update', function(data) {
        $broadcast("new message", data);
    })
}

In the controller:

$on("new message", function(new_messages){
    // here the view will update with new messages
    $scope.messages = new_messages; 
}

Upvotes: 2

Martin
Martin

Reputation: 16300

This is a natural pattern for Angular.

Your views should be bound to controllers. Those controllers can have the your service injected into them.

YourService.$inject = ['socket'];
function YourService(socket) {
    this._subscribers = [];

    var self = this;

    socket.on('update', function(data) {
        this.updateSubscribers(data);
    })
}

YourService.prototype.subscribe = function(callback) {
    this.subscribers.push(callback);
}

YourService.prototype.updateSubscribers = function(data) {
    this.subscribers.forEach(function(subscriber) {
        subscriber(data);
    }) 
}

angular.module('yourModule').service('yourService', yourService);

This assumes your socket is registered in the injector as socket.

Then in your controllers you can inject this service and subscribe to asynchronous updates. Be sure to call $scope.apply() in the controller to trigger the update in Angular.

 YourController.$inject = ['yourService', '$scope'];
 function YourController(yourService, $scope) {

      this.data;

      var self = this;

      yourService.subscribe(function(data) {
          self.data = data;
          $scope.$apply();
      });
 } 

If in ngRouter or uiRouter this controller is configured as YourController as vm:

<div>{{ vm.data }}</div>

Now on any async update this view will update.

Upvotes: 0

Related Questions