Search code examples
javascriptangularjsscorm2004

Testable method for calling global javascript functions in angularjs controllers


Very Global Description

You have functions that must exist in the global scope. You want a way to encapsulate the functionality of these functions in a dependency injectable way so they are more testable. What is the correct way to do this?

Somewhat Specific Description

I have a situation where I have an existing javascript library that uses a lot of variables and function in the global scope. The library is a SCORM engine and part of the standard dictates that the functions must be available in the global scope (so user created content can access them).

However, there are a couple of places in various controllers that I must call them as well. At the moment I an just calling them, but this makes testing those lines difficult and seems to violate the angular mindset in general.

Plnkr

A plunk just illustrating how things are now

var globalVariable = 1;

function globalFunction(){
  return "cats";
}

var app = angular.module('plunker', []);

app.controller('MainCtrl', [ '$scope', function($scope) {
  $scope.name = 'World';
  $scope.cats = globalFunction(); //This line is hard to test and makes me feel dirty.
}]);

A plunk that wraps the function in a factory (maybe what is recommended in comments?)

var globalVariable = 1;

function globalFunction(){
  return "cats";
}

var app = angular.module('plunker', []);

app.factory('angularFriendly', function(){
  this.globalFunction = globalFunction;
  return this;
});

app.controller('MainCtrl', ['$scope', 'angularFriendly',
  function($scope, angularFriendly) {
    $scope.name = 'World';
    $scope.cats = angularFriendly.globalFunction();
  }
]);

This is more or less what I mean in my original comment when I said I considered wrapping it in a service. I make the function injectable (which is good and is much more testable) but I am not sure it is good practice (because I have just moved the difficult to test code to a different place).


Solution

  • I prefer wrapping 3rd party non-Angular applications into value objects (which is functionally the same as using a factory and immediately calling it prior to injection, which feels cleaner).

    var globalVariable = 1;
    
    function globalFunction(){
      return "cats";
    }
    
    var app = angular.module('plunker', []);
    app.value('external', globalFunction);
    
    app.controller('MainCtrl', ['$scope', 'external',
      function($scope, external) {
        $scope.name = 'World';
        $scope.cats = external();  // Treat the dependency as a service to resolve into the scope
      }
    ]);