16

I have next service:

angular.module('app').service('BaseService', function (alertService) {
   var service = {};
   service.message =  "Hello";
   service.perform = function () {
        alertService.add("success",service.message);
   };
   return service;
});

Now I want to inherit this service in some 'ChildService' with overriding message on "World!". I expect that calling ChildService.perform() will show alert with "World!".

What is proper way to do this?

Alexey
  • 449
  • 1
  • 7
  • 13
  • Use your favorite flavor of javascript inheritance, and then add the resulting objects as services. Keep in mind, since services are singletons, you would either want to add a single instance, or a constructor. – Sacho Oct 29 '14 at 23:09

4 Answers4

32

AngularJS does not provide any mechanism to implement inheritance of services directly, however for your case you can use $provide.decorator to extend BaseService itself or use it like a prototype of another ChildService using plain JavaScript. In my practice, in order to have service with configurable state and behaviour I use providers. In all of the following examples the console output will be World.

Decorator

If you don't need the original BaseService in your module, you can decorate it

Plunker

function AlertService() {
  this.add = function(level, message) {
    switch(level) {
      case 'success':
        console.log(message);
    }
  }
}

function BaseService(alertService) {
  this.message =  "Hello";
  this.perform = function () {
    alertService.add("success",this.message);
  };
}

angular.
  module('app',[]).
  config(['$provide', function($provide) {
    $provide.decorator('BaseService', function($delegate) {
      $delegate.message = 'World';
      return $delegate;
    });
  }]).
  service('alertService', AlertService).
  service('BaseService', ['alertService',BaseService]).
  controller('ctrl', ['BaseService', function(baseService) {
    baseService.perform();
  }]);

Prototypical Inheritance

Plunker

function AlertService() {
  this.add = function(level, message) {
    switch(level) {
      case 'success':
        console.log(message);
    }
  }
}

function BaseService(alertService) {
  this.message =  "Hello";
  this.perform = function () {
    alertService.add("success",this.message);
  };
}

function ChildService(BaseService) {
  angular.extend(ChildService.prototype, BaseService);
  this.message = "World";
}

angular.
  module('app',[]).
  service('alertService', AlertService).
  service('BaseService', ['alertService',BaseService]).
  service('ChildService', ['BaseService',ChildService]).
  controller('ctrl', ['ChildService', function(ChildService) {
    ChildService.perform();
  }]); 

Provider

Plunker

function AlertService() {
  this.add = function(level, message) {
    switch(level) {
      case 'success':
        console.log(message);
    }
  }
}

function BaseService() {
  var message =  "Hello";

  this.setMessage = function(msg) {
    message = msg;
  }

  function Service(alertService) {
    this.perform = function () {
      alertService.add("success", message);
    };
  }

  function Factory(alertService) {
    return new Service(alertService);
  }

  this.$get = ['AlertService', Factory];
}

angular.
  module('app',[]).
  provider('BaseService', BaseService).
  config(['BaseServiceProvider', function(baseServiceProvider) {
    baseServiceProvider.setMessage('World');
  }]).
  service('AlertService', AlertService).
  controller('ctrl', ['BaseService', function(baseService) {
    baseService.perform();
  }]);
Vadim
  • 8,701
  • 4
  • 43
  • 50
  • 3
    decorators are not a form of inheritance - it is strictly a decoration of the given service, e.g. in inheritance you have two final artifacts: BaseService <- ChildService inheriting, whereas with decoration you only have BaseService, after it was decorated. – Sacho Oct 29 '14 at 23:35
  • 2
    @Sacho I'm aware that decorators are not form of inheritance, however I claim that in order to get service that is a bit different from some other service (and another service is not in a use in it's original form) inheritance is not needed and decorator of existing service may be used instead. – Vadim Oct 29 '14 at 23:56
15

I would modify a little bit your code:

app.factory('BaseService', function () {
   //var service = {}; 
   function service(){
       this.message = "hello";
   }; 
   service.prototype.perform = function () {
        console.log('perfom', this.message);
   };
   return new service();
});

(I just change your alertService for an console.log();.. )

then implement inheritance like this:

app.factory('childBaseService',['BaseService', function(BaseService){
    var childBaseService = function(){
            BaseService.constructor.call(this)
            this.message = 'world!';
    };

    childBaseService.prototype = Object.create(BaseService.constructor.prototype);
    childBaseService.prototype.constructor = childBaseService;

    return new childBaseService();

}]);

You could see a example of how this works.. at the end, BaseService and childService would be instances of BaseService constructor ( service ).

console.log(BaseService instanceof BaseService.constructor); //true
console.log(childBaseService instanceof BaseService.constructor); //true
rahpuser
  • 1,224
  • 10
  • 31
  • This is a neat way to go "in-house"(doing things within service declarations), I hadn't thought of it :) – Sacho Oct 30 '14 at 06:57
5

Here is an example, based on Constructor/new inheritance(which I would generally recommend against).

BaseService.$inject = ['alertService']
function BaseService(alertService) {
    this.message = 'hello'
    this.alertService = alertService
}

BaseService.prototype.perform = function perform() {
    this.alertService.add("success",this.message);
}


ChildService.$inject = ['alertService']
function ChildService(alertService) {
    this.message = 'hello world'
    this.alertService = alertService
}

ChildService.prototype = Object.create(BaseService.prototype)

And then you would just include these as services:

angular.module('app')
    .service('BaseService', BaseService)
    .service('ChildService', ChildService)
Sacho
  • 2,169
  • 1
  • 14
  • 13
  • Can you elaborate on your "I would generally recommend against" comment? This seems to me like it would work well. – Noah Solomon Oct 29 '14 at 23:27
  • 1
    Inheritance is awkward in general - it's only beneficial in very specific cases, compared to its very prolific use. Javascript inheritance is an extra awkwardness layer on top of it, due to the way `this` is bound on function calls. Constructor-based/classical inheritance in javascript adds an extra layer of confusion(e.g. note how I have to use Object.create to extend the prototyped objects, consider the work you would have to do to implement super() calls, the need to .bind() functions that would be used as callbacks, the list goes on). Object composition works smoother. – Sacho Oct 29 '14 at 23:34
  • 1
    nice example. I believe it should be `this.message` rather than `service.message` as you didn't use a service object. – Greg Oct 30 '14 at 00:41
  • There is no `Base` class in your example, therefore `Object.create(Base.prototype)` will throw an error. I believe it should be `Object.create(BaseService.prototype)` instead. – Vadim Oct 30 '14 at 00:47
  • Your CHildService is losing it constructor.. after prototyping you should do childService.prototype.constructor = childService, also you are missing an important fact in inheritance.. the call for the super class constructor, with child.prototype = object.create(base.prototype) you pass the prototypes properties and functions to a new object.. but the properties and functions of Base it self are not inherited. – rahpuser Oct 30 '14 at 01:04
  • Thanks for the comments - I modified the example accordingly to remove the silly mistakes :). @rahpuser - I don't really think `.constructor` is very important, because 1) this is meant to be a simple example, and 2) I prefer to avoid the `.constructor` property(and this style of inheritance in general!). After all, we're talking about setting up inheritance for *singletons*. It just seems silly to me. – Sacho Oct 30 '14 at 06:53
2

Module A with service ASvc:

(function(angular) {
  var app = angular.module('A', []);

  app.service('ASvc', ['$http', function($http) {
     var ASvc = {
       list: function() {
         return $http({
           method: 'GET',
           url: '/A'
         });
       },

       getInstructions: function(id) {
         return $http({
           method: 'GET',
           url: '/instructions/' + id
         });
       }
     };
     return ASvc;
  }]);
})(angular);

Module B with service BSvc which inherits from ASvc:

(function(angular) {
  var app = angular.module('B', ['A']);

  app.service('BSvc', ['$http', 'ASvc', function($http, ASvc) {
     var BSvc = {
       list: function() {
         return $http({
           method: 'GET',
           url: '/B'
         });
       }
     };

     BSvc.__proto__ = ASvc; // here you're settting the inheritance
     return BSvc;
  }]);
})(angular);

Now, when you call BSvc.getInstructions(30775);, you're calling the parent's service (ASvc) getInstructions function from BSvc, and when you call BSvc.list(), you're calling a method which was overridden from ASvc in BSvc. Inheritance.

And BTW, when I'm passing angular as argument to the closure, instead of referring to the global angular variable directly from within it, I'm allowing code minifiers and obfuscators to do things like this:

(function(j){var c=j.module('A',[]);})(angular); // and so on

It's a good thing to have in mind and I consider it being a good practice ;)

Bruno Finger
  • 2,105
  • 3
  • 27
  • 47
  • 1
    The obfuscator argument doesn't count here: `1.` Your code uses about ~15 additional characters. `2.` if you don't want to pollute the window namespace, you can simply chain those calls and save additional characters: `angular.module('A',[]).service(...).service(...);` – Benjamin M Aug 14 '16 at 03:49
  • 1
    @BenjaminM OK maybe the obfuscation isn't perfect, but minification works very well. Chaining calls produces very ugly code on my opinion, and since most of the time the code I write will be processed into a concatenation and minification, I care a lot about the legibility of the original code. And about the closure, even if it would refer the global 'angular' variable within, it's a habit I teached myself to do, wrap modules within closures. In the worst case, it simply won't hurt. – Bruno Finger Aug 14 '16 at 08:24