2

Apologies for the code heavy post but I wanted to provide as much context as possible. I am having an issue with defining a service in my Angular.js application. Services are supposed to act as singletons throughout an application (source), so I am very confused to be getting the following behavior.

In my app.js file, I run my AmplitudeService Service and console.log(AmplitudeService). This outputs an object with all of the methods that I have defined within my AmplitudeService.js file. As such, I am able to properly use the service and log events as expected.

However, when I console.log(AmplitudeService) within my header.js, it outputs my Window object. As such, the Window does not contain functions such as "logEvent", "identifyUser", etc., so AmplitudeService is not usable in that case.

Would appreciate any and all insight!

AmplitudeService.js (source)

Note: If you check the author's syntax, he returns an object at the end of his service. In my research, I've read to use the "this" keyword when defining Service functions (source), and that you don't need to return an object as you would with a Factory, so I have updated it accordingly.

angular.module('AmplitudeService', [])
.service('AmplitudeService', 
['$amplitude', '$rootScope', 'amplitudeApiKey', '$location',
 function ($amplitude, $rootScope, amplitudeApiKey, $location) {

  this.init = function() {
    $amplitude.init(amplitudeApiKey, null);
    $amplitude.logEvent('LAUNCHED_SITE', {page: $location.$$path});
  }

  this.identifyUser = function(userId, userProperties) {
    $amplitude.setUserId(userId);
    $amplitude.setUserProperties(userProperties);
  }

  this.logEvent = function(eventName, params) {
    $amplitude.logEvent(eventName, params);
  }
}]);

angular-amplitude.js (source)

This allows access to "$amplitude" throughout the application

(function(){
var module = angular.module('angular-amplitude', ['ng']);

module.provider('$amplitude', [function $amplitudeProvider() {
    this.$get = ['$window', function($window) {
      (function(e,t){
        var r = e.amplitude || {};
        var n = t.createElement("script");
        n.type = "text/javascript";
      n.async = true;
      n.src = "https://d24n15hnbwhuhn.buttfront.net/libs/amplitude-2.2.0-min.gz.js";
      var s = t.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(n,s);
      r._q = [];

      function a(e){
        r[e] = function(){
          r._q.push([e].concat(Array.prototype.slice.call(arguments,0)));
        }
      }
      var i = ["init","logEvent","logRevenue","setUserId","setUserProperties","setOptOut","setVersionName","setDomain","setDeviceId","setGlobalUserProperties"];
      for(var o = 0; o < i.length; o++){
        a(i[o])
      }
      e.amplitude = r
    }
      )(window,document);
      return $window.amplitude;
    }];
}]);
return module;
}());

App.js

angular.module('app', [
'ngRoute',
'angular-amplitude',
'AmplitudeService',
])

 .run(['AmplitudeService', function(AmplitudeService){
 console.log(AmplitudeService); // Outputs 'Object {}'
 AmplitudeService.init();
 AmplitudeService.logEvent('LAUNCHED_SITE');
 console.log(AmplitudeService); // Outputs 'Object {}'
 }])

Header.js

angular.module('app.common.header', [])
.controller('HeaderCtrl', [ '$rootScope', '$scope', '$location', '$scope', '$route', '$window', 'AmplitudeService', function($rootScope, $scope, $location, $route, $window, AmplitudeService){

$scope.goToSearch = function(term) {
$location.path('/search/' + term);
 console.log(AmplitudeService); // Outputs 'Window {}'
};
}]);
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
zcoon
  • 174
  • 11

1 Answers1

3

Looks like you are injecting $scope in your controller, causing the unexpected mapping of the variable

angular.module('app.common.header', [])
.controller('HeaderCtrl', [ '$rootScope', '$scope', '$location', **'$scope'**, '$route', '$window', 'AmplitudeService', function($rootScope, $scope, $location, $route, $window, AmplitudeService){

$scope.goToSearch = function(term) {
$location.path('/search/' + term);
 console.log(AmplitudeService); // Outputs 'Window {}'
};
}]);

as an aside if find this format far easier to read / work with when defining controllers

angular
  .module('app.common.header', [])
  .controller('HeaderCtrl', headerController);

headerController.$inject = [ '$rootScope', '$scope', '$location', '$route', '$window', 'AmplitudeService']

function headerController($rootScope, $scope, $location, $route, $window, AmplitudeService){
  $scope.goToSearch = function(term) {
    $location.path('/search/' + term);
    console.log(AmplitudeService); // Outputs 'Window {}'
  };
}
Ant Kennedy
  • 1,230
  • 7
  • 13
  • I think I need $scope, but I could be wrong. All of my functions (which I have stripped to cut down the amount of code in this post) within Header.js are listed as $scope functions. For example, $scope.goToSearch(term) would go to my Search page and search for the term. This function is called in the HTML using ng-submit = "goToSearch(term)". – zcoon May 24 '16 at 16:46
  • I have removed the $scope injection, but now I have 7 undefined functions within that controller. Any other suggestions? I appreciate the previous comment. – zcoon May 24 '16 at 16:49
  • 2
    You have injected `$scope` twice - have a look where I have added the `**'$scope'**` in your example (first code sample). Remove this definition and you should be good to go. – Ant Kennedy May 24 '16 at 16:49
  • 2
    Angular doesn't care about the names of the functions, it maps them in the order they are defined i.e.`['$scope', function(a){console.log(a)}]` a in this instance will be $scope – Ant Kennedy May 24 '16 at 16:51
  • Wow, unreal. You are totally right and I am now getting the expected behavior. Thank you so much. – zcoon May 24 '16 at 16:52
  • curious if you could provide any insight here? : http://stackoverflow.com/questions/37421245/angularjs-amplitude-service-not-acting-as-singleton – zcoon May 24 '16 at 19:09