5

I have a factory that needs to listen for a broadcast event. I injected $scope into the factory so I could use $scope.$on. But as soon as I add $scope to the parameter list I get an injector error.

This works fine:

angular.module('MyWebApp.services')
    .factory('ValidationMatrixFactory', ['$rootScope', function($rootScope) {
        var ValidationMatrixFactory = {};
        return ValidationMatrixFactory;
    }]);

This throws an injector error:

angular.module('MyWebApp.services')
    .factory('ValidationMatrixFactory', ['$scope', '$rootScope', function($scope, $rootScope) {
        var ValidationMatrixFactory = {};
        return ValidationMatrixFactory;
    }]);

Why can't I inject $scope into a factory? And if I can't, do I have any way of listening for events other than using $rootScope?

Legion
  • 3,922
  • 8
  • 51
  • 95
  • 3
    `$scope` is only for controllers and views. A factory is not connected with a view. That's why you can't inject it. There is nothing wrong with listening for events on `$rootScope` in a factory. – Pop-A-Stash Sep 19 '16 at 15:46

2 Answers2

8

Because $scope is used for connecting controllers to view, factories are not really meant to use $scope.

How ever you can broadcast to rootScope.

$rootScope.$on()
Niles Tanner
  • 3,911
  • 2
  • 17
  • 29
  • I've read that using `$rootScope.$on()` is bad because the listeners never go out of scope causing the event to fire multiple times. Does that not apply here? – Legion Sep 19 '16 at 15:48
  • Yes, his can happen, as JoelCDoyle has said it's probably fine and for a factory, necessary. I would refer to this post for more information. – Niles Tanner Sep 19 '16 at 15:52
  • `$rootScope.$on` can be bad in the context of a controller, but not in a factory where there is no `$scope` to "go out of" – Pop-A-Stash Sep 19 '16 at 15:59
  • @NilesTanner There's no link to the post in your comment. – Legion Sep 19 '16 at 16:10
  • @Legion whoops sorry about that. Here is it: http://stackoverflow.com/questions/28124442/angularjs-events-broadcasted-emitted-on-rootscope-occur-multiple-times – Niles Tanner Sep 19 '16 at 16:21
1

Even though you can't use $scope in services, you can use the service as a 'store'. I use the following approach inspired on AltJS / Redux while developing apps on ReactJS.

I have a Controller with a scope which the view is bound to. That controller has a $scope.state variable that gets its value from a Service which has this.state = {}. The service is the only component "allowed" (by you, the developer, this a rule we should follow ourselves) to touch the 'state'.

An example could make this point a bit more clear

(function () {
    'use strict';
    angular.module('app', ['app.accounts']);  
  
    // my module... 
    // it can be defined in a separate file like `app.accounts.module.js`
    angular.module('app.accounts', []);
 
    angular.module('app.accounts')
        .service('AccountsSrv', [function () {
            var self = this;

            self.state = {
                user: false
            };
            
            self.getAccountInfo = function(){
              var userData = {name: 'John'};   // here you can get the user data from an endpoint
              self.state.user = userData;      // update the state once you got the data
            };
        }]);
    
    // my controller, bound to the state of the service
    // it can be defined in a separate file like `app.accounts.controller.js`
    angular.module('app.accounts')
        .controller('AccountsCtrl', ['$scope', 'AccountsSrv', function ($scope, AccountsSrv) {
            $scope.state = AccountsSrv.state;
          
            $scope.getAccountInfo = function(){
                // ... do some logic here
                // ... and then call the service which will 
                AccountsSrv.getAccountInfo();
            }
        }]);
})();
<script src="https://code.angularjs.org/1.3.15/angular.min.js"></script>
<div ng-app="app">
  <div  ng-controller="AccountsCtrl">
    Username: {{state.user.name ? state.user.name : 'user info not available yet. Click below...'}}<br/><br/>
    <a href="javascript:void(0);" ng-click="getAccountInfo()">Get account info</a>
  </div>
</div>

The benefit of this approach is you don't have to set $watch or $on on multiple places, or tediously call $scope.$apply(function(){ /* update state here */ }) every time you need to update the controller's state. Also, you can have multiple controllers talk to services, since the relationship between components and services is one controller can talk to one or many services, the decision is yours. This approach focus on keeping a single source of truth.

I've used this approach on large scale apps... it has worked like a charm.

I hope it helps clarify a bit about where to keep the state and how to update it.

JorgeObregon
  • 3,020
  • 1
  • 12
  • 12