Dependency Injection: Angular vs. RequireJS

Share this article

If you have built large JavaScript applications before, chances are, you have faced the task of managing component dependencies. You can think of a component as a block of functionality. It may be a function, object, or an instance. The block chooses to expose one or more public methods. It may also choose to hide non-public functionality. In this article, we will look at two major libraries, AngularJS and RequireJS. We will analyse how they use dependency injection to share components across an application.

Short Story on Dependency Injection

Dependency injection becomes a necessity when you need an easy way to pull in one or more components into an application. For example, assume you have two components named database and logger. Assuming that the database component exposes the methods getAll, findById, create, update, and delete. The logger component only has one method, saveNewLog, in it’s public API. Let’s assume the logger component depends on the database component to function. Using dependency injection, we could pass in the database component as a dependency to the logger component during creation.

Just so you can visualize the dependencies better, I’ll write it in code. Note that the actual syntax depends on the dependency injection library you use. Angular and RequireJS have different syntax, so the code below is a generic example and we’ll get to actual representations of the two libraries in a bit.

Here is the database API:

function database() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
}

And, here is the logger API:

function logger(database) {
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

As you can see, we are passing the database component into the constructor of the logger. The part of the application which handles the instantiation of the logger must provide it with an instance of a database component.

The Need for Dependency Injection

Now that we are more educated about what dependency injection is, let’s identify what benefits it brings to the table. If you are an advocate of good JavaScript design, some benefits of dependency injection may be obvious to you. If they’re not, let me explain a few of the general benefits. I believe these apply across the board whether you use AngularJS or RequireJS.

Testing Becomes a Breeze

Testing becomes much easier because you can provide mocked dependencies instead of real implementations.

Separation of Concerns

Dependency injection lets you separate the parts of your application so that each one handles a distinct job. In the above example, the database module is only concerned with dealing with a database. The logger module is only responsible for logging data, whether it is in a database, file, or the console. The benefit of this is easier swapping of dependencies. If we later decide that we need to use a file-based database instead of a traditional relational database, we just have to pass in a different module. This module just has to exposes the same API methods as the database module, and the logger module would continue to work properly.

Easier Reusability of Components

Due to this nature of separating concerns, we can reuse components. This makes it easy to reuse external libraries which also follow the same pattern.

Dependency Management Libraries

We’ve seen some of the benefits, now let’s compare two major libraries in the game – Angular and RequireJS. RequireJS is dedicated to dependency management. AngularJS provides much more than dependency management, but we’ll only focus on that capability.

AngularJS

AngularJS has these things called recipes. A recipe is analogous to a component which was described earlier on. Examples of Angular components are factories, directives, and filters. Angular provides several ways to inject a component into something else. We will use the database and logger components as an example.

Before we dive into the different ways to do dependency injection with Angular, let’s build our example scenario first. Assuming we have an Angular module named myModule, let’s create a UserController:

function UserController() {
  //some controller logic here
}

We also have database and logger services defined:

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

myModule.factory('logger', function(){
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
});

Let’s assume the UserController depends on the logger component to function. Of course, the logger component still depends on the database component. We can represent the dependencies in AngularJS in three different ways.

Parameter Name Inferring

This method depends on the names of function parameters when reading in dependencies. We can apply it to the example above like this:

function UserController(logger) {
  //some controller logic here to use injected logger factory
}

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

myModule.factory('logger', function(database) {
  //use injected database factory here
  var publicApis = {
      saveNewLog: function() {}
  };

  return publicApis;
});

Using $inject

This dependency injection method uses $inject property on the function of your component. The $inject property should be an array of strings specifying the dependencies. For the UserController, this is easy to do. For the logger factory we’ll need to change the example above a little bit so we can add the property to its function. Since it’s an anonymous function, we should first define it as a named function. Next, we can attach the required property, as shown below.

function UserController(logger) {
  //some controller logic here to use injected logger factory
}

UserController['$inject'] = ['logger'];

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

function loggerFactory(database) {
  //use injected database factory here
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

loggerFactory['$inject'] = ['database'];
myModule.factory('logger', loggerFactory);

Using Array Notation

The third way involves passing in an array as the second parameter when defining the UserController and the logger factory. Here, we also have to change the way we define the UserController so we can use this method.

function UserController(loggerFactory) {
  //some controller logic here to use injected logger factory
}

myModule.controller('UserController', ['logger', UserController]);

myModule.factory('database', function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {} 
  };

  return publicApis;
});

function loggerFactory(database) {
  //use injected database factory here
  var publicApis = {
    saveNewLog: function() {}
  };

  return publicApis;
}

myModule.factory('logger', ['database', loggerFactory]);

RequireJS

Dependency Injection with RequireJS works by having components in files. Each component lives in its own separate file. Whereas AngularJS loads the components upfront, RequireJS only loads a component when needed. It does this by making an Ajax call to the server to get the file where the component lives.

Let’s see how RequireJS handles dependency injection syntactically. I will skip over how to setup RequireJS. For that, please refer to this SitePoint article.

The two main functions concerned with RequireJS dependency injection are define and require. In short, the define function creates a component, while the require function is used to load a set of dependencies before executing a block of code. Let’s inspect these two functions in a bit more depth.

The define Function

Sticking with the logger and database example, let’s create them as components (the filename: comments indicate where we would actually define the components):

//filename: database.js
define([], function() {
  var publicApis = {
    getAll: function() {},
    findById: function(id) {},
    create: function(newObject) {},
    update: function(id, objectProperties) {},
    delete: function(id) {}
  };

  return publicApis;
});

//filename: logger.js
define(['database'], function(database) {
  //use database component here somewhere
  var publicApis = {
    saveNewLog: function(logInformation) {}
  };

  return publicApis;
});

As you can see, the define function takes two parameters. The first one is an optional array of components which must be loaded before the component can be defined. The second parameter is a function which must return something. You may notice that we are passing in the database component as a dependency for defining the logger module. The database component does not rely on any other component. Hence, its define function takes an empty array as the first argument.

The require Function

Now, let’s look at a scenario where we make use of the defined components. Let’s simulate logging some information. Since we need the logger component to be able to make use of its code, we must pull it in using the require function.

require(['logger'], function(logger) {
  //some code here
  logger.saveNewLog('log information');
});

As you can see above, the require function is only used to run some code and does not return anything. The first parameter it accepts is an array of dependent modules. The second is the function to run when those dependencies have been loaded. This function accepts as many parameters as there are dependencies to load. Each one represents the corresponding component.

Conclusion

This brings us to the end of this comparison between AngularJS and RequireJS when it comes to dependency injection. Although the two take fairly different approaches, there’s no reason why they cannot work together. Please let us know what your experience is using these two libraries or if you have anything else to add.

Lamin SannehLamin Sanneh
View Author

An emberjs enthusiast, equally fascinated by laravel's clever but unorthodox use of fascades. Lamin Sanneh is a front end web developer who loves to dive into pretty much every thing web related. He teaches on youtube and you can also find him talking about web stuff here to anyone who would listen.

ColinIdependency injectionLearn AngularRequireJS
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week