AngularJS: Always place a Facade between your injectable Service and Controller/another injectable Service
Business logic outside of controllers
It is a good practice in AngularJS (and in software development at all) to place any business logic outside of a controllers, in separated Services. It makes our code easier to test and easier to figure out what is going on— if you have bug in HTML bindings you don’t have to care about source of your data, and if you have something wrong with logic you don’t see all binding noise.
Switching between different implementations
But maybe it should go one step further? In my company we experienced few weird behaviors of asynchronous HTTP calls. The logic was when PUT was sent to server, all data should be reloaded — application has things dependent on others, when one thing changes another should also change. This logic exists on server side for a while, it was much easier to do not duplicate logic but reload everything. Problem was that when PUT came to the application and GET was send, another PUT was sent by application but GET came in, for user it looks like value was reverted. Of course there is many solution to this problem, like queuing or elimination of outdated GET but we were on the trenches and we needed some light solution. And we chose to switch from Service which use asynchronous $http service to plain XMLHttpRequest, which can be synchronous. But how to do it, without touching existing logic?
Facade design pattern
Then we realized that we can use Facade design pattern to hide these details. Facade can registered to AngularJS as factory, like this:
angular.module(‘app’).factory(“CommunicationServiceFacade”, CommunicationServiceFacade);
It has injected both Angular’s $http and plain XMLHttpRequest versions and they can be switched internally with ease:
function CommunicationServiceFacade(HttpService, PlainJsService) { var delegate = HttpService; //just place here PlainJsService
//to switch CommunicationServiceFacade = {}; CommunicationServiceFacade.update = function(newValue) {
return delegate.update(newValue);
}
return CommunicationServiceFacade;
}
All controllers/services which use this facade were not changed in any way. Of course there were few quirks we had to deal with, $http uses Promises and XMLHttpRequest not and others, but all in all it gave us a way to switch not only between different implementation of making HttpRequest, but also with different data sources — maybe in future controller’s code will be reused in desktop version written with Electron? You never know what might happen in future, business requirements can change dramatically and we have to keep it in our minds.
Conclusion
I think it’s not a bad idea to place a Facade between AngularJS service, native like $location or written by developer, and something which uses it. It gives a possibility to leave your existing code alone when something needs to be changed. Of course it means that you have twice more files than without it, if something change in its interface you have to change it in two more places — but I think it’s reasonable price for goods which Facade can give you.