I'm using the socket.ioclient in my Angular WebApp. I wrapped it up in a service, but that might not necessarily be of interest. However, since the incoming data is in a different scope than the controller which is using the service, I always have to call $scope.$apply. Even worse I found a few situations (during connect/reconnect) where I would have to use a safeApply as explained here.
I understand that this is an Angular Anti-Pattern, however I don't see a way around this.
Is there a general way to solve this (preferably inside the service) that does not pollute the controllers with a lot of $scope.$apply/safeApply?
BR,
Daniel
Here is also some code, working, but not nice:
angular.module('mean')
.controller('ConnectionStateController', function ($scope) {
$scope.safeApply = function (fn) {
var phase = this.$root.$$phase;
if (phase == '$apply' || phase == '$digest') {
if (fn && (typeof (fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
var socket = io.connect('http://localhost:3000');
$scope.message = 'Not connected';
socket.on('connect', function () {
$scope.safeApply(function () {
$scope.message = "Connected";
});
});
});
In your case, you know that socket.io operations are always going to be run asynchronously and that makes it a valid place to use $scope.$apply (towards the bottom of the page).
I would recommend not using safeApply and explicitly calling $scope.$apply. Using safeApply is going to cover up for genuine mistakes in implementation. Also, if you are wrapping your code in a service, you can inject the $rootScope in the service and call the async functions in $rootScope.$apply. Then you would not need to call $scope.$apply in each controller.
I'll answer the question myself, since I have a working solution using defers. Nevertheless I'm not sure if I did it right. Here's the service:
angular.module('mean')
.factory('ConnectionService', function ($q, $timeout) {
var bindings = {};
var socket = io.connect('http://localhost:3000');
// catch all events by overriding the socket's $emit
var $emit = socket.$emit;
socket.$emit = function (event, obj) {
console.log('***', 'on', '"' + event + '"', obj);
if (bindings.hasOwnProperty(event)) {
bindings[event].notify(obj);
}
$emit.apply(socket, arguments);
};
return {
socket: socket,
observe: function (event, callback) {
if (!bindings.hasOwnProperty(event)) {
bindings[event] = $q.defer();
}
bindings[event].promise.then(null, null, function (data) {
callback.apply(this, [ data ]);
});
}
};
});
This allows a controller to use the service like this:
ConnectionService.observe('news', function (data) {
$scope.message = data;
});
Not sure, if that's a nice solution or if it has side effects, that I don't see yet.
BR,
Daniel
Related
My team wants to move to a style of dependency injection that is closer to the CommonJS/Node JS syntax for out Angular codebase:
var myDependency = require('myDependency');
I've started using $injector.get() directly inside at the top of my functions, so far with no obvious trouble. That means I've converted this:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['serviceA', 'serviceB', 'serviceC', function(serviceA, serviceB, serviceC) {
//My code here
}])
Into:
angular.module('modD', ['modA', 'modB', 'modC'])
.service('serviceD', ['$injector', function($injector) {
var serviceA = $injector.get('serviceA');
var serviceB = $injector.get('serviceB');
var serviceC = $injector.get('serviceC');
//My code here
}]);
Is there something I'm missing. Does not declaring the required dependencies outside of the function definition cause any sort of performance issues?
Note: this answer has not been tested.
After digging into the angular's code, I find this:
// in function createInjector
function provider(name, provider_) {
...
if (isFunction(provider_) || isArray(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
...
return providerCache[name + providerSuffix] = provider_;
}
...
function factory(name, factoryFn, enforce) {
return provider(name, {
$get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn
});
}
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
...
// in function createInternalInjector
function invoke (...){
...
for (i = 0, length = $inject.length; i < length; i++) {
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key, serviceName)
);
}
...
return fn.apply(self, args);
}
function instantiate(...){
...
var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
...
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
Its calls seems like:
service invoked factory invoked instantiate
factory invoked provider invoked instantiate
So I think your questions is equal to Is calling $injector.get() one by one has the same performance as calling $injector.instantiate() once?
As the code shows, instantiate invoked invoke which actually invoked getService for each services injected by you. And $injector.get is just binding to getService.
So the answer to my equal question is True.
And the answer to your question is No, their performance is very close.
Please correct me if I'm wrong, thank you!
There are no significant difference between both pieces of code in the case of a service. $injector.get(...) call doesn't provide any overhead. But it provides a significant amount of extra characters per dependency.
There is a difference when the same thing is done with injectables that have local dependencies - controllers and route/state resolvers.
When these dependencies
app.controller('SomeCtrl', function ($scope, service) { ... });
are replaced with $injector.get(...), it will choke on $scope - it is local dependency. And with other dependencies being retrieved like that, controller loses its testability. The dependencies cannot be mocked with
$controller('SomeCtrl', { service: mockedService });
I personally don't see how $injector.get(...) may benefit the style of the project (and haven't seen a good style guide that would suggest it).
Node uses require function because it works for it, not because it is better or cooler. The alternative would be to pack each Node script into into AMD-like wrappers, which would be painful. Fortunately, we already have wrappers around Angular units!
I'm trying to encapsulate the events in a service in order to implement a mechanics to subscribe / unsubscribe the listeners when a controller's scope is destroyed. This because I have been using the rootScope.$on in the following way:
if(!$rootScope.$$listeners['event']) {
$rootScope.$on('event', function(ev, data){
// do some...
});
}
or
$scope.$on('$destroy', function(ev, data){
// unsubscribe the listener
});
So I just need one listener of this event, I need to delete the existing listener when the controller is no longer alive, because the function I registered earlier is still being triggered.
So I need to implement a $destroy event listener on my controller, to destroy the listener when the scope is destroyed, but I don't want to do that code each time I create an event.
That's why I want to create a service in where I'm going to encapsulate the events.
angular.module('core').factory('event', [
function() {
var service = {};
service.events = {};
service.on = function(scope, eventId, callback) {
scope.$on('$destroy', function(ev, other){
//unsubscribe
});
service.events[eventId] = callback;
// scope = null; I guess ?
};
service.emit = function(eventId, data){
if (service.events[eventId])
service.events[eventId](data);
else
return new Error('The event is not subscribed');
};
return service;
}
]);
This could be done using $rootScope instead of my own methods but encapsulating the $on and $emit of $rootScope, but at the end I'll have the same issue here.
So these are my questions:
Is a good practice to pass the scope ref value to a service?
What is the meaning of $$destroyed? when this is true means that angularJS has no internal references to the instance?
Should I do a scope = null in my service to let GC delete the object or does angularJS handle an explicit delete?
Is there a better way to do what I want?
What you are trying to accomplish is basically an event bus.
You have also described very well what is wrong with the current implementation.
A different way to approach the problem is to decorate the $rootScope with your bus (or any other event bus for that matter). Here is how:
app.config(function ($provide) {
$provide.decorator('$rootScope', ['$delegate', '$$bus', function ($delegate, $$bus) {
Object.defineProperty($delegate.constructor.prototype, '$bus', {
get: function () {
var self = this;
return {
subscribe: function () {
var sub = $$bus.subscribe.apply($$bus, arguments);
self.$on('$destroy',
function () {
console.log("unsubscribe!");
sub.unsubscribe();
});
},
publish: $$bus.publish
};
},
enumerable: false
});
return $delegate;
}]);
});
Considering the following $$bus implementation (kept basic for simplicity):
app.factory('$$bus', function () {
var api = {};
var events = {};
api.subscribe = function (event) {
if (!events.hasOwnProperty(event.name)) {
events[event.name] = [event];
} else {
events[event.name].push(event);
}
return {
unsubscribe: function () {
api.unsubscribe(event);
}
}
};
api.publish = function (eventName, data) {
if (events.hasOwnProperty(eventName)) {
console.log(eventName);
angular.forEach(events[eventName], function (subscriber) {
subscriber.callback.call(this, data);
});
}
};
api.unsubscribe = function (event) {
if (events.hasOwnProperty(event.name)) {
events[event.name].splice(events[event.name].indexOf(event), 1);
if (events[event.name].length == 0) {
delete events[event.name];
}
}
};
return api;
});
Now all you have to do is subscribe or publish events. The unsubscribe will take place automatically (when the $scope is destroyed):
$scope.$bus.subscribe({
name: 'test', callback: function (data) {
console.log(data);
}
});
And later on publish an event:
$scope.$bus.publish('test', {name: "publishing event!"});
An important point to make is that the events themselves are subscribed to each individual $scope and not on the $rootScope. That is how you "know" which $scope to release.
I think it answers your question. With that in mind, you can obviously make this mechanism much sophisticated (such as controller event listener released when a view routed, unsubscribe automatically only to certain events, etc.).
Good luck!
** This solution is taken form Here which uses a different bus framework (other then that it is the same).
I recently dug a little deeper into unit testing. I was wondering if there is a way to use spies in production code as well. I've a tracking service. It would be nice to access other services and maybe even controllers, without haveing to alter their code.
Is there a way to spy on methods being called from services and controllers in the application code and what would be the best way to do so?
EDIT
Atm. I'm using this pattern for spying on services:
var vSetFNTrigger = function (sEvent, fnTrigger) {
fnTrigger.obj[fnTrigger.sMethod] = (function () {
var fnCached = fnTrigger.obj[fnTrigger.sMethod];
return function () {
$rootScope.$broadcast(sEvent, {});
return fnCached.apply(this, arguments);
};
})();
};
fnTrigger: {
obj: formData, // the service
sMethod: 'qPost' // the method to spy on
},
EDIT 2
I forgot to add a return to the inner function.
There should be nothing stopping you from doing this, although I think it is the wrong tool for the job.
If you are in Angular, you should consider using a decorator pattern. You can even use the provider decorator to intercept pretty much anything in Angular.
For instance, you might have a spy function that looks like this:
function createSpy(serviceName, source, spyNames, rootScope) {
var spy = angular.extend(angular.isFunction(source) ? function () {
console.log("Called " + serviceName + '()', arguments);
// broadcast with rootScope
return source.apply(source, arguments);
} : {}, source);
spyNames.forEach(function(name) {
var original = spy[name];
spy[name] = function() {
console.log("Called " + serviceName + '.' + name, arguments);
// broadcast with rootScope
return original.apply(spy, arguments);
};
});
return spy;
}
Then, you can create a generic function to generate a decorator:
function decorateWithSpy($provide, service, spyNames) {
$provide.decorator(service, function($delegate, $rootScope) {
return createSpy(service, $delegate, spyNames, $rootScope);
});
}
You can configure your spies like this:
app.config(function($provide) {
decorateWithSpy($provide, '$http', ['get']);
decorateWithSpy($provide, '$compile', []);
});
Doing this causes all of my $http and $compile functions to get printed to the console.
I know there are several approaches to loading-indicators in angular js (this one, for example: https://gist.github.com/maikeldaloo/5140733).
But they either have to be configured for every single call, or - if they act globally, as I want - just apply to http-requests, but not to $q-promises being used in services.
The global loading indicators, I've seen so far, work with
$httpProvider.responseInterceptors.push(interceptor);
Is there something similiar for $q, like a $qProvider.reponseInterceptors? And if not, what would be the most convenient way to implement such a functionality? Is it possible to use a decorator-pattern of some kind for example?
Although I find it very complicated, unnecessary and probably broken, you could decorate $q and override its defer function.
Every time someone asks for a new defer() it runs your own version which also increments a counter. Before handing out the defer object, you register a finally callback (Angular 1.2.0 only but always may fit, too) to decrement the counter.
Finally, you add a watch to $rootScope to monitor when this counter is greater than 0 (faster than having pendingPromisses in $rootScope and bind like ng-show="pendingPromisses > 0").
app.config(function($provide) {
$provide.decorator('$q', ['$delegate', '$rootScope', function($delegate, $rootScope) {
var pendingPromisses = 0;
$rootScope.$watch(
function() { return pendingPromisses > 0; },
function(loading) { $rootScope.loading = loading; }
);
var $q = $delegate;
var origDefer = $q.defer;
$q.defer = function() {
var defer = origDefer();
pendingPromisses++;
defer.promise.finally(function() {
pendingPromisses--;
});
return defer;
};
return $q;
}]);
});
Then, view bound to a scope that inherits from $rootScope can have:
<span ng-show="loading">Loading, please wait</span>
(this won't work in directives with isolate scopes)
See it live here.
There is a good example in the official documentation working for the current stable 1.2.0.
http://docs.angularjs.org/api/ng.$http (top quarter of the page, search for Interceptors)
My extraction of these documentation lead me to this solution:
angular.module('RequestInterceptor', [])
.config(function ($httpProvider) {
$httpProvider.interceptors.push('requestInterceptor');
})
.factory('requestInterceptor', function ($q, $rootScope) {
$rootScope.pendingRequests = 0;
return {
'request': function (config) {
$rootScope.pendingRequests++;
return config || $q.when(config);
},
'requestError': function(rejection) {
$rootScope.pendingRequests--;
return $q.reject(rejection);
},
'response': function(response) {
$rootScope.pendingRequests--;
return response || $q.when(response);
},
'responseError': function(rejection) {
$rootScope.pendingRequests--;
return $q.reject(rejection);
}
}
});
You might then use pendingRequests>0 in an ng-show expression.
Since requested by the OP, this is based on the method we are using for the app we are currently working on. This method does NOT change the behaviour of $q, rather adds a very simple API to handle promises that need some kind of visual indication. Although this needs modification in every place it is used, it is only a one-liner.
Usage
There is a service, say ajaxIndicator, that knows how to update a portion of the UI. Whenever a promise-like object needs to provide indication until the promise is resolved we use:
// $http example:
var promise = $http.get(...);
ajaxIndicator.indicate(promise); // <--- this line needs to be added
If you do not want to keep a reference to the promise:
// $http example without keeping the reference:
ajaxIndicator.indicate($http.get(...));
Or with a resource:
var rc = $resource(...);
...
$scope.obj = rc.get(...);
ajaxIndicator.indicate($scope.obj);
(NOTE: For Angular 1.2 this would need tweeking, as there is no $then() on the resource object.)
Now in the root template, you will have to bind the indicator to $rootScope.ajaxActive, e.g.:
<div class="ajax-indicator" ng-show="ajaxActive"></div>
Implementation
(Modified from our source.) WARNING: This implementation does not take into account nested calls! (Our requirements called for UI blocking, so we do not expect nested calls; if interested I could try to enhance this code.)
app.service("ajaxIndicator", ["$rootScope"], function($rootScope) {
"use strict";
$rootScope.ajaxActive = false;
function indicate(promise) {
if( !$rootScope.ajaxActive ) {
$rootScope.ajaxActive = true;
$rootScope.$broadcast("ajax.active"); // OPTIONAL
if( typeof(promise) === "object" && promise !== null ) {
if( typeof(promise.always) === "function" ) promise.always(finished);
else if( typeof(promise.then) === "function" ) promise.then(finished,finished);
else if( typeof(promise.$then) === "function" ) promise.$then(finished,finished);
}
}
}
function finished() {
$rootScope.ajaxActive = false;
}
return {
indicate: indicate,
finished: finished
};
});
I had the same big question few weeks ago and I happen to make some directives to represent the loading state on the action buttons and ng-repeat content loading.
I just spent some time and pushed it on github:
https://github.com/ocolot/angularjs_loading_buttons
I hope it helps.
I'm using BreezeJS with AngularJS but I'm having a difficult time understanding how to get Promises to work with $scope. Whenever I try to submit my form its not showing the validation errors until I click it for a 2nd time. I realize I could call $scope.$apply() but I read its not best practice? Here is my code:
app.controller("MainController", ["$scope", "$q", "datacontext", function ($scope, $q, datacontext) {
datacontext.manager.fetchMetadata();
$scope.errors = [];
$scope.addDamp = function () {
var item = datacontext.manager.createEntity("Damp", {
name: $scope.newDamp
});
var tes = datacontext.manager.saveChanges()
.then(function () {
alert("yay");
})
.fail(function (error, a, b, c) {
var arr = [];
error.entitiesWithErrors.map(function (entity) {
entity.entityAspect.getValidationErrors().map(function (validationError) {
arr.push(validationError.errorMessage);
});
});
$scope.errors = arr;
datacontext.manager.rejectChanges();
});
};
}]);
What is the best way to go about handling scope changes that come from inside of a Promise?
Yes, you're going to need $scope.apply here, because the promise isn't coming out of a core Angular call (such as $http, which would have handled the .apply() itself behind the scenes). In fact, the Breeze/Angular example on the BreezeJS page (http://www.breezejs.com/samples/todo-angular) includes a $scope.apply() after its data retrieval:
datacontext.getAllTodos()
.then(success)
.fail(failed)
.fin(refreshView);
function refreshView() {
$scope.$apply();
}
It's a bad practice to toss $scope.apply() about where you don't need it. But when you're handling promises created outside of Angular itself, it's going to come up.