How to properly integrate non-AngularJS libraries in your AngularJS application

How to properly integrate non-AngularJS libraries in your AngularJS application
Photo by Mika Baumeister / Unsplash

There may be times when you need to use a non-AngularJS library such as lodash or underscore in your AngularJS application.

If you use a bundler like JSPM, webpack or browserify, you can  simply include the library in your bundle.

But what if you need to load the library from an external file or a CDN?

Let's assume that we want to use a CDN-hosted version of lodash in our AngularJS application:

<script src="//some-cdn.com/lodash.js"></script>

The tempting way

Because lodash makes itself available as a global variable, you may be tempted to use as described in this StackOverflow example:

// Assume that _ is magically available
function SomeController($scope){
  $scope.sum = _.add(6,4);
}

// Define dependencies
SomeController.$inject = ['$scope'];

// Register controller
ngModule.controller('SomeController', SomeController)

The caveat here is that you assume that lodash is magically available, which is not guaranteed to be the case.

What if lodash failed to load correctly? Or what if the CDN is down?

As soon as your AngularJS code relies on _, it will crash with an error: _ is not defined.

This may cause very unpredictable application behavior and terrible user experience as the user may be confronted with a page that is rendered partially or incorrectly.

Notice how the page is partially rendered while the application crashed. View live demo.

Can we do better? Yes we can!

A better way

Instead of assuming that _ is magically available as a global variable, we create a factory that returns _ from the $window object:

function LodashFactory($window) {
  if(!$window._){
    // If lodash is not available you can now provide a
    // mock service, try to load it from somewhere else,
    // redirect the user to a dedicated error page, ...
  }
  return $window._;
}

// Define dependencies
LodashFactory.$inject = ['$window'];

// Register factory
ngModule.factory('_', LodashFactory);

This allows us to inject _ anywhere in our application and rest assured that _ is available as expected if injection succeeded:

// Inject _ using dependency injection
function SomeController($scope, _){
  $scope.sum = _.add(6,4);
}

// Define dependencies
SomeController.$inject = ['$scope', '_'];

// Register controller
ngModule.controller('SomeController', SomeController)

Notice that we now inject _ using AngularJS dependency injection instead of assuming that it is magically available in the controller.

If lodash is not available we can decide to provide a mock service or even try to load it from somewhere else, depending on your application requirements.

Bonus advantage

Because _ is now provided by the AngularJS dependency injector, you can easily mock it in your unit tests in case you want to run your unit tests on a machine that does not have access to the CDN.

Summary

By providing non-AngularJS libraries as a service using the angular.factory method, you can control what happens in case the library fails to load. This prevents your application from crashing unexpectedly and greatly enhances user experience.

At the same time you benefit from the power of the AngularJS dependency injector, allowing you to mock the service in unit tests when the CDN may not be available.

How cool is that!