106

Is it correct to pass the "current" $scope to an AngularJS service?

I'm in the situation where I've a $service knowing it's consumed by only one controller, and I'd like to have a reference to the controller's scope in the $service methods themselves.

Is this philosophically correct?

Or I'd better to broadcast events to the $rootScope and then make my controller listen to them?

Ahmad Baktash Hayeri
  • 5,802
  • 4
  • 30
  • 43
S.C.
  • 2,844
  • 3
  • 26
  • 27
  • 1
    Can you perhaps let us know more specifically what you are attempting to do ? Perhaps pushing scope onto the service is not necessary at all? – ganaraj Mar 19 '13 at 20:26
  • Well, it's not so hard. Simply, I'd want to be able to access the `$scope` properties and call `$scope.$apply` when needed. – S.C. Mar 19 '13 at 20:36
  • Plus, say that I want to apply changes that comes from the $service onto the $scope. Hope it's clearer now. – S.C. Mar 19 '13 at 20:39
  • 8
    I suggest you put the $scope properties that you want your service to access into the service itself (instead of having them in the controller). Services are better places to store models/data than controllers. – Mark Rajcok Mar 19 '13 at 21:13
  • @MarkRajcock I'm trying to understand this issue as well. Currently I'm just calling a service and attaching the served data to the controller's `$scope`...how would the controller directly access the data in the service and pass it to the view without doing this? – cortexlock Dec 18 '14 at 21:41

4 Answers4

67

To let the controller know when something async happens, use Angular promises.

To provoke the $apply, you don't need the scope, you can call $rootScope.$apply, as there is no difference calling it in a specific scope or in the root.

Regarding the variable reading, it would be better if you received parameters. But you could also read it from a scope as an object parameter, but I would go with parameter, that would make your service interface much more clearer.

Caio Cunha
  • 23,326
  • 6
  • 78
  • 74
  • I think this is the answer that better solves my AngularJS beginner doubts. – S.C. Mar 19 '13 at 23:56
  • @Caio Cunha Could you expand on why it's not a good idea to pass a scope? I'm having exactly this issue, I want to add some stuff to `$scope` via a call to a service using an async `executeSql()` function. Looking into 3 options (1) use a callback on the async function, then call `$scope.$apply`...this works, but is ugly (2) pass `$scope` to the async function, then call `theScope.$apply()`...this also works (3) use a promise...not tried this yet. Why is a promise the best way? Thanks! – cortexlock Dec 18 '14 at 20:11
15

I would say if your functionality is specific to one controller only than you don't need a service.

The controllers tasks is to manipulate the specific model whereas a service should deal with global tasks. I would rather stick to this paradigm instead of mixing things up.

This is what the docs say

Service

Angular services are singletons that carry out specific tasks common to web apps

Controller

In Angular, a controller is a JavaScript function(type/class) that is used to augment instances of angular Scope, excluding the root scope.

PS: Apart from that if you need to digest you can also inject the $rootScope within your service.

F Lekschas
  • 12,481
  • 10
  • 60
  • 72
  • 2
    Thanks. Great reply. Going deeply into my situation, the point is that I'm using Service because I want a singleton. There's only one controller consuming the service, but this controller could be instantiated several times during the app lifecycle, so I really want the Service to be always in the same state. – S.C. Mar 19 '13 at 23:53
  • Anyway the clarification about calling `$apply` or `$digest` to the $rootScope make completely sense to me. – S.C. Mar 19 '13 at 23:55
  • 1
    I would still keep it separate into a Service for testing. – bluehallu Feb 17 '14 at 14:48
  • If the functionality is specific to one *instance* of a controller then you could skip implementing a service, but otherwise no as you sacrifice the ability to have state shared between those instances. Further, just because there's a single type of controller today, it does not mean that there won't be a different controller that can leverage the functionality tomorrow. Additionally, a service can avoid bloating the controller. So it's not so much a question of whether there's a single controller, but what the functionality is that's in question. – Nick Sep 19 '18 at 09:08
9

Yes. You can pass the $scope into the service when you initialize it. In the service constructor you can assign the scope to something like this._scope and then reference the scope within the service!

angular.module('blah').controller('BlahCtrl', function($scope, BlahService) {

    $scope.someVar = 4;

    $scope.blahService = new blahService($scope);

});

angular.module('blah').factory('blahService', function() {

    //constructor
    function blahService(scope) {
        this._scope = scope;

        this._someFunction()
    }

    //wherever you'd reference the scope
    blahService.prototype._someFunction = function() {

        this._scope['someVar'] = 5;

    }

    return blahService;

});
user12121234
  • 2,519
  • 2
  • 25
  • 27
  • 1
    +1, however, I would like to see a way to automatically know each controller's `$scope` whenever that given controller injects the service -- so as to not have to call a method on the service and manually pass `$scope` to it. – Cody Sep 25 '14 at 21:45
  • 2
    @Cody I don't recommend that as it is contradictory to Dependency Injection – Coldstar May 12 '15 at 23:31
  • Agreed, +1 for shooting me down! Probably butchers DIP also -- I would say, at the risk of being redundant. – Cody May 13 '15 at 00:37
  • You are mixing services with factories here. This solution uses a factory, which differs from a service. The main difference is that a service returns a (singleton) object. Whereas a factory returns a function, which can be instantiated (`new MyFunction()`). The question was regarding a service, where calling `new` is not an option. – Karvapallo Aug 23 '15 at 16:34
  • @Karvapallo Good point. As a counter point I believe angular services refer to a factory, service, and provider (ng-wat)? – user12121234 Aug 23 '15 at 17:52
  • @wbeange heehee... touché! If only the AngularJS docs would just call them e.g. providers (instead of services) there would be less confusion :) – Karvapallo Aug 24 '15 at 20:12
  • In your example, `BlahService` should actually be `blahService`. When you `return blahService;`, you are returning the constructor, correct? So the only way to use the service is to use that constructor, correct? – toddmo Nov 19 '15 at 18:52
  • Does this approach creates circular reference? – Ganesh Matkam Aug 06 '16 at 10:53
6

I personally believe that passing the whole $scope to a service is a bad idea, because it creates a kinda circular reference: the controller depends on the service and the service depends on the scope of the controller.

On top of being confusing in terms of relations, things like this one end up getting in the way of the garbage collector.

My preferred approach is to put a domain object in the controller scope and pass that to the service. This way the service works regardless whether it's used inside a controller or maybe inside another service in the future.

For example, if the service is supposed to push and pop elements from an array errors, my code will be:

var errors = [];
$scope.errors = errors;
$scope.myService = new MyService(errors);

The service interacts then with the controller by operating on errors. Of course I've got to be cautious about never wiping out the whole array reference, but at the end of the day that's a general JS concern.

I'd never want to use broadcasting, $apply and/or similar things, because imho good OO-practices will always trump whatever Angular-magics.

Marco Faustinelli
  • 3,734
  • 5
  • 30
  • 49
  • 1
    Does this code have any difference with this one?: `$scope.errors = []; $scope.myService = new MyService($scope.errors);` – Soldeplata Saketos Aug 21 '17 at 11:51
  • @SoldeplataSaketos - Yes, it does. `errors` lives independently of `$scope`. That's the whole point of this answer. Pls check the link I provided in the text. Cheers. – Marco Faustinelli Aug 24 '17 at 05:44
  • 1
    If I understand correctly your code, `$scope.errors` is pointing to `var errors`, and the variable errors looks redundant to me, as it's just another pointer. One similar situation I can think of and that it's blatantly redundant is this piece of code: `const errors = errors2 = errors3 = []; $scope.errors = errors;` . Do you agree that with only the piece of code you provided it seems that `var errors = []` is redundant? – Soldeplata Saketos Aug 24 '17 at 06:36
  • No, it's not. I repeat myself word by word: `errors` lives independently of `$scope`. You need to understand what a domain object is, as well as what a `var` assignment is. If the link I provided is not enough, there's plenty of other material available. – Marco Faustinelli Sep 12 '17 at 10:27
  • Well, that will depend solely on the implementation of the function `MyService(errors)` . In my understanding, the service should be generating a logging array based on the parameter (that is in this case a pointer). For me that's a bad pattern, as the services are singletons in angular. If the implementation of the service is well programmed, it should generate the array in an internal variable (to keep being a singleton). Therefore, it has no sense to initialize the variable outside of the service. – Soldeplata Saketos Sep 25 '17 at 14:35
  • Bla bla bla, no arguments at all – Soldeplata Saketos Sep 26 '17 at 17:18
  • ```class MyService { constructor(storage) { if (!MyService.instance) { MyService.instance = this; } this.log = storage; return MyService.instance; } } let $scope = {}; let errors = $scope.errors = [1,2,3]; $scope.errors = [4] console.log($scope.errors) let logger = new MyService(errors) errors = [5,6]; console.log(logger.log) let logger2 = new MyService($scope.errors) console.log(logger2.log)``` – Soldeplata Saketos Sep 27 '17 at 09:58
  • execute the code above and you will see that the errors don't live no more in `var errors`. – Soldeplata Saketos Sep 27 '17 at 09:59