Reputation: 17626
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
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
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