2

I´ve been working with Angularjs for a couple of months now, and I was wondering how to implement efficient OOP using this framework.

I am working in a project where I need to instantiate a "terminal" class, which has constructor properties and a series of methods (as a usual class). In normal JS I would use the pseudoclassical pattern (separating the constructor properties from methods in the prototype chain).

From I know about Angularjs, model.service() would be the best choice, as I create a new instance of the service each time I call it. But I've usually seen the next implementation when it comes to define the methods:

myApp.service('helloWorldFromService', function() {
    this.sayHello = function() {
        return "Hello, World!"
    };
});

But, wouldn't it create the function sayHello() everytime I call the class? I was wondering if it would be better to separate the functions, something like:

myApp.service('helloWorldFromService', function() {
    this.msg= "Hello, World!";
});

helloWorldFromService.prototype.showMessage = function() {
    return this.msg;
};

In that way, the function showMessage() will be created just once in memory, and shared across all instances of the services created.

Also, if this is possible (and if it really makes the code more efficient), what would be the way to implement it? (the code above is just a wild guess)

Thank you

  • 1
    *"everytime I call the class"* ... no, services are singletons. To do second version you need to pass named function reference to service instead of anonymous function – charlietfl Mar 10 '16 at 19:18
  • Your are right, I didn't understand well the puropose of a service. Look at the comment I made to rob's answer – Gabriel Requena García Mar 11 '16 at 12:31

2 Answers2

2

Edit in response to comment: Looks like if you simply return the constructor function for your object you can do this, then access the prototype through the call to the service.

Quick explanation, in the example, clicking "Get Again" will call the prototype function changeHelloWorldString and update the string with the name of the control. Once you click "Change Prototype", that changeHelloWorldString function is changed to append " [PROTOTYPE CHANGE]" to the string. Clicking either "Get Again" button will demonstrate that the prototype change made in the second controller affected the prototype chain for the object in both controllers.

See the example below:

angular.module('myModule2', [])
  .factory('myService', function() {
    function FactoryConstructor(thirdFunction) {
      this.helloWorldFunction = function() {
        return this.helloWorldString;
      }
      this.thirdFunction = thirdFunction;
    }
    FactoryConstructor.prototype.helloWorldString = 'Hello World';
    FactoryConstructor.prototype.changeHelloWorldString = function(newString) {
      this.helloWorldString = newString;
    };
    FactoryConstructor.prototype.changeThirdFunction = function(newFunction) {
      this.thirdFunction = newFunction;
    }
    return FactoryConstructor;
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    // console.log(factoryResult instanceof myService) //tested true
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl1 String');
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = new myService(function() {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
    // console.log(factoryResult instanceof myService) //tested true
    $scope.getAgain = function() {
      factoryResult.changeHelloWorldString('ctrl2 String');
      factoryResult.thirdFunction();
      factoryResult.changeThirdFunction(function() {
        this.helloWorldString += ' third';
      });
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
    
    $scope.changePrototype = function() {
      myService.prototype.changeHelloWorldString = function(newString) {
        this.helloWorldString = newString + " [PROTOTYPE CHANGE]";
      }
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
    <button ng-click='changePrototype()'>Change Prototype</button>
  </div>
</div>

Another excellent explanation of this is found in this answer. This may show a better/cleaner way to do what this example shows by using a provider (which service and factory are both derived from, see the side note from the author).

Rest of original post below for background

Angular services are singletons that can be injected into many places. So if you do this:

angular.module('myModule', [])
.service('myService', function() {
  var myService = this;
  this.helloWorldString = 'Hello World String';
  this.helloWorldFunction = function() {
    return myService.helloWorldString;
  }
})
.controller('main', function($scope, myService) {
  $scope.getAgain = function() {
    $scope.hwString = myService.helloWorldString;
    $scope.hwString2 = myService.helloWorldFunction();
  }
  $scope.getAgain();
})
.controller('notMain', function($scope, myService) {
  myService.helloWorldString = 'edited Hello World String';
  $scope.hwString = myService.helloWorldString;
  $scope.hwString2 = myService.helloWorldFunction();
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule'>
  <div ng-controller='main'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='notMain'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
  </div>
</div>

You'll notice that initially the two are different, because the first pair was obtained before the change, but the change made in the second controller DOES, in fact, affect the first. Just click the Get Again button and it will re-pull the info from the service and it now matches, proving they're the same object despite being injected into two different controllers.

Looks like what you really want is a factory (although this is mostly semantics, you can change out "factory" for "service" in this next example and it will produce the same result. This can also be seen in the Angular docs themselves. The documentation for a service never actually uses .service, it uses .factory throughout). That way you can, in essence, construct a new instance of your factory object when you call, in this example, 'myService(...)'. Using those function parameters, you can customize properties, including functions, of the object you return, as you can see in the example.

angular.module('myModule2', [])
  .factory('myService', function() {
    return function(stringInput, thirdFunction) {
      return {
        helloWorldString: stringInput,
        helloWorldFunction: function() {
          return this.helloWorldString;
        },
        thirdFunction: thirdFunction
      }
    }
  })
  .controller('ctrl1', function($scope, myService) {
    var factoryResult = myService('Hello World String', function () {
      this.helloWorldString += ' first';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  })
  .controller('ctrl2', function($scope, myService) {
    var factoryResult = myService('new Hello World String', function () {
      this.helloWorldString += ' second';
    });
    $scope.hwString = factoryResult.helloWorldString;
    $scope.hwString2 = factoryResult.helloWorldFunction();
  
    $scope.getAgain = function() {
      factoryResult.thirdFunction();
      $scope.hwString = factoryResult.helloWorldString;
      $scope.hwString2 = factoryResult.helloWorldFunction();
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='myModule2'>
  <div ng-controller='ctrl1'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
  <div ng-controller='ctrl2'>
    <div>{{hwString}}</div>
    <div>{{hwString2}}</div>
    <button ng-click='getAgain()'>Get again</button>
  </div>
</div>
Community
  • 1
  • 1
devtanc
  • 136
  • 1
  • 7
  • Yes, this is what I was looking for. I guess I didn't understand well the differences between both of them. But now I ask the same question for that approach: ¿wouldn't it create the function `helloWorldFunction` twice, one for each instance created? Isn't there a way to use prototypal inheritance for those functions? thank you – Gabriel Requena García Mar 11 '16 at 10:22
  • I see what you're talking about. I'll do some playing around with this. I have an idea for how you might be able to do this in a more Angular-y way. I'll edit once I've had a chance to test it out. – devtanc Mar 11 '16 at 16:16
  • @GabrielRequenaGarcía That should answer the question the way you're hoping. Let me know if you need more. The real trick here is that Angular is still Javascript. Sometimes, because it's frequently discussed in terms of JS over here and Angular over there, we can forget that. Services simply return a Javascript object, but they do so as a singleton, which is why this example works. The "myService" service returns a **singleton** constructor function which allows you to instantiate off it, and access the prototype chain effectively through it. – devtanc Mar 11 '16 at 17:16
  • @GabrielRequenaGarcía In fact, another solution to the issue (non-Angular-y) is to simply add another JS file that has the constructor function. You can then access this file from within your Angular code. This is the same way you can include the underscore library and access it throughout your Angular code as well. But the presented solution is the way you can do it natively in Angular. – devtanc Mar 11 '16 at 17:23
  • Great! Thanks! and great link to that other answer! definately worth checking it out – Gabriel Requena García Mar 13 '16 at 18:02
0

The service function will only be called once so your "class methods" will only be created once with both of your approaches. However I wouldn't use your first approach for the following reasons.

  • you can't use inheritance
  • you have to put your entire class in 1 big function
  • you have to be careful not to call a function before it is defined in the constructor.

Instead I would do something more like your second approach. e.g.

myApp.service('helloWorld', HelloWorldService);

function HelloWorldService() {
    this.msg = "Hello, World!";
}

HelloWorldService.prototype.showMessage = function() {
    return this.msg;
};

Or in ES6 you could do

myApp.service('helloWorld', HelloWorldService);

class HelloWorldService {
    constructor() {
        this.msg = "Hello, World!";
    }

    showMessage() {
        return this.msg;
    }
}

EDIT

If you want to be able to get a new instance of your class each time it is injected you could wrap it with a factory. I added $log to show how DI would work:

myApp.factory('buildHelloWorld', function($log) {
    return function() {
        return new HelloWorld($log);
    }
});

function HelloWorld($log) {
    this.msg = "Hello, World!";
    $log.info(this.msg);
}

HelloWorld.prototype.showMessage = function() {
    return this.msg;
};

Then you can inject the buildHelloWorld() function into a controller and call it to get an instance of HelloWorld.

//controller
var myCtrl = function(buildHelloWorld) {
    this.helloWorld = buildHelloWorld();
}
rob
  • 17,995
  • 12
  • 69
  • 94
  • You are right rob, but I guess that was I was looking for was a factory, as I need to create different instances of an object which share common methods. I didn't have very clear the difference between factory and services. But your approach is indeed the good way of defining a service, making the code clearer and modulable. In fact, I searched through the code of Angularjs and they recommend to define a service nearly the same as you did: – Gabriel Requena García Mar 11 '16 at 09:56
  • [link](https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.js) in lines 4087 - 4122 (better open with a notepad) – Gabriel Requena García Mar 11 '16 at 10:06
  • @GabrielRequenaGarcía quite the opposite. A `service` lets you use `new` to create individual instances whereas a factory returns and obect – charlietfl Mar 11 '16 at 13:09
  • @GabrielRequenaGarcía I added an edit that shows how to get a new instance every time with a factory but maybe you already figured this out – rob Mar 11 '16 at 17:42
  • @rob, yes I've seen in the source how it creates a new instance using `new` when calling a service, but it's only created in the definition of the service, not everytime you call it as a dependency in a controller. That's what happened in the original example of tanc's answer, that when I modified some property, it affected to all instances. Your edit is what I was looking for, but I marked tanc's answer as correct because, apart from explaining that same approximation to the answer, the link he provided to another answer is just great. Thanks! – Gabriel Requena García Mar 13 '16 at 18:00