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?

1
  • 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. Commented Oct 29, 2014 at 23:09

4 Answers 4

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();
  }]);
Sign up to request clarification or add additional context in comments.

2 Comments

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 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.
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

1 Comment

This is a neat way to go "in-house"(doing things within service declarations), I hadn't thought of it :)
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)

6 Comments

Can you elaborate on your "I would generally recommend against" comment? This seems to me like it would work well.
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.
nice example. I believe it should be this.message rather than service.message as you didn't use a service object.
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.
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.
|
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 ;)

2 Comments

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(...);
@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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.