1

I need to write code which accesses data in a backend.

Trouble is that backend is not ready yet. So I want to inject a service which would, possibly by configuration, use the real backend or a mock object.

I think angular services are what I need. But I am not clear how to implement this.

app.factory('myService', function() {
  var mySrv;

  //if backend is live, get the data from there with $http
  //else get data from a mock json object
  return mySrv;
});

Somehow I guess I'd have to write two more services, the real one and the fake one, and then call the one or the other in 'myService'?

Maybe I totally misunderstand mocking, but I'd rather not want this to be mocked for test runs (not for unit tests like in this post: Injecting a mock into an AngularJS service - I'd like the app to really use my mock for demo and development purposes.

Community
  • 1
  • 1
transient_loop
  • 5,984
  • 15
  • 58
  • 117

2 Answers2

1

This is actually where Angular's dependency injection system really comes in handy. Everything in Angular is basically a provider at some level. Methods like service and factory are just convenience functions to avoid boilerplate code.

A provider can be configured during the bootstrap process, which is really handy for setting up scenarios exactly like what you are describing.

At it's simplest, a provider just needs to be a constructor function that creates an object with a $get function. The $get is what creates the actual services, and this is where you can check to see which one to create.

//A simple provider
function DataServiceProvider(){

  var _this = this;

  _this.$get = function(){
    //Use configured value to decide which
    // service to return
    return _this.useMock ? 
             new MockService() : 
             new RealService;
  };

}

Now you can register this as a provider with your application module.

angular.module('my-module', [])
  .provider('dataService', DataServiceProvider);

And the provider can be configured before it creates the first service instance. By convention, the provider will be available as NAME + 'Provider'

angular.module('my-module')
  .config(['dataServiceProvider', function(dataServiceProvider){
    //Set the flag to use mock service
    dataServiceProvider.useMock = true;
  }]);

Now whenever you inject dataService anywhere in your application, it will be using the mock service you provided based on configuration.

You can see a full working example of this in the snippet below.

(function(){

  function RealService(){
    this.description = 'I\'m the real McCoy!';
  }
  
  function MockService(){
    this.description = 'I\'m a shady imposter :P';
  }
  
  function DataServiceProvider(){
    var $this = this;
    
    $this.useMock = false;
    
    $this.$get = function(){
      return $this.useMock ?
          new MockService() :
          new RealService();
    };
  }
  
  function CommonController(dataService){
    this.dataServiceDescription = dataService.description;
  }
  CommonController.$inject = ['dataService'];
  
  angular.module('common', [])
    .provider('dataService', DataServiceProvider)
    .controller('commonCtrl', CommonController);
  
  angular.module('provider-app-real-service', ['common'])
    .config(['dataServiceProvider', function(dataServiceProvider){
      dataServiceProvider.useMock = false;
    }]);
  
  angular.module('provider-app-mock-service', ['common'])
    .config(['dataServiceProvider', function(dataServiceProvider){
      dataServiceProvider.useMock = true;
    }]);
  
  
  var moduleNames = ['provider-app-real-service','provider-app-mock-service'];
    
  angular.forEach(moduleNames, function(modName){
    //Have to manually bootstrap because Angular only does one by default
    angular.bootstrap(document.getElementById(modName),[modName]);
  }); 
  
}());
<script src="http://code.angularjs.org/1.3.0/angular.js"></script>
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet" />

<div class="container">
  <div class="row" id="provider-app-real-service">
    <div class="col-sm-12" ng-controller="commonCtrl as ctrl">
      <h1>{{ctrl.dataServiceDescription}}</h1>
    </div>
  </div>
  <div class="row" id="provider-app-mock-service">
    <div class="col-sm-12" ng-controller="commonCtrl as ctrl">
      <h1>{{ctrl.dataServiceDescription}}</h1>
    </div>
  </div>
</div>
Josh
  • 44,706
  • 7
  • 102
  • 124
  • This looks pretty much like what I am looking for. I will go through your detailed answer in order to understand it, and then possibly accept it. Thank you! – transient_loop Oct 21 '14 at 02:11
  • Just imagine you will have to do for every service that needs mocking in your application. You will repeat the same thing everywhere. Every developer needs to be aware of this anti pattern of newing up with condition. You coul d easily tie this up with syntactic sugar and automatic mocking. Creating instance with if else... not so impressive imho. But this will work with some maintenance headache.. – PSL Oct 21 '14 at 04:03
  • http://stackoverflow.com/questions/25537396/issue-with-manual-bootstrapping-and-overriding-angular-services-in-config . I also was in need of similar stuff but I could reach till here. Never got a chance to investigate a spe ific issue I was facing. Though it works superbly in my application with around 30 odd data services:) I will also look forward to an update on this answer with a much neat approach or any other answer to this question probably might help me as well. :) – PSL Oct 21 '14 at 04:13
  • @PSL - I don't think it's an anti-pattern if you are only concerned about one service. Your question is pretty different because you are trying to change how your entire application works. It's certainly a noble goal, but I might consider writing an HttpInterceptor to use mock data instead of trying to mock every service. Also, the issue with module ordering and the injector is something I have written about before: http://www.technofattie.com/2013/04/12/angular-modules-and-dependency-injection.html – Josh Oct 21 '14 at 04:43
  • @Josh `I don't think it's an anti-pattern if you are only concerned about one service` That really rarely is the case right? just one service? My point was that it should be the responsibility of injector to provide you instance which acts as your dependency container. Probably my opinion, when i see new-ing up i see it as a code smell. Yes my case is probably bit different, but you cannot really do it better with an interceptor, i had looked at that before itself.You should really not write an interceptor to abstract out the service logic :) – PSL Oct 21 '14 at 13:19
  • In fact the first thing i had tried was exactly the way you have in your answer. . And well my mock services are more complex it really has caching as its first class citizen and has its own logic to manage cruds as a mock. I saw you blog, nicely written!! But pretty basic, the ordering you have mentioned, is the obvious way and that exactly is what i am utilizing in my approach as well. and that is not what my issue really is, i need to dig deeper for that in the angular bootstrapping and injection which i have not got a chance to do yet. – PSL Oct 21 '14 at 13:20
  • 1
    @PSL - I've been thinking about this some and am pretty intrigued at a nice way of solving the issue. I wouldn't mind talking more about it. I think that there might be a way to co-opt the creation of the injector and somehow insert a `mocks` module in to override existing components, but I'm not 100% sure how yet. Need to dig into the Angular source code a little more. I think the solution would be useful for many people :) – Josh Oct 21 '14 at 14:24
  • @Josh i second that... :) I agree to the fact that it could be really useful for many people with less overhead of maintaining and still write the services that you can easily mock with just some configuration... :) That's my goal too.. Off-late i did not get a chance to investigate further. – PSL Oct 21 '14 at 14:31
0

Does this need to be a service? If you want to create a mock service then Josh's answer above is perfect.

If your not tied to using a service then I suggest looking at my answer to the following question Mock backend for Angular / Gulp app which also about mocking out a backend. Regardless of wether your backend is created or not mocking it out allows for more stable test runs and development of you app.

Community
  • 1
  • 1
cconolly
  • 366
  • 1
  • 9