JavaScript
Article

Using RequireJS in AngularJS Applications

By Ravi

While writing large JavaScript applications, one of the simplest things one can do is divide the code base into several files. Doing this improves maintainability of the code but increases the chances of missing or misplacing the script tag on your main HTML document. Keeping track of the dependencies becomes difficult as the number of files increases. This issue persists in large AngularJS applications as well. We have a number of tools in place that take care of loading dependencies in the application.

In this article, we will see how to use RequireJS with AngularJS to make the job of loading dependencies simpler. We’ll also examine how to use Grunt to generate combined files containing the RequireJS modules.

A Brief Introduction to RequireJS

RequireJS is a JavaScript library that helps in lazily loading JavaScript dependencies. Modules are just JavaScript files with some RequireJS syntactic sugar in them. RequireJS implements Asynynchronous Modules specified by CommonJS. RequireJS offers simple APIs to create and refer to modules.

RequireJS needs a main file that contains the basic configuration data such as paths to modules and shims. The following snippet shows the skeleton of a main.js file:

require.config({
  map:{
    // Maps
  },
  paths:{
    // Aliases and paths of modules
  },
  shim:{
    // Modules and their dependent modules
  }
});

All modules in the application need not be specified in the paths section. Others can be loaded using their relative paths. To define a module, we need to use the define() block.

define([
  // Dependencies
], function(
  // Dependency objects
){

  function myModule() {
    // Can use the dependency objects received above
  }

  return myModule;
});

A module may have some dependent modules. In general, an object is returned at the end of a module, but it is not mandatory.

Angular’s Dependency Injection vs RequireJS Dependency Management

One of the common questions that I hear from Angular developers regards the difference between Angular’s dependency management and that of RequireJS. It is important to remember that the purpose of both the libraries is totally different. The dependency injection system built into AngularJS deals with the objects needed in a component; while dependency management in RequireJS deals with the modules or, JavaScript files.

When RequireJS attempts to load a module, it checks for all dependent modules and loads them first. Objects of loaded modules are cached and they are served when same modules are requested again. On the other hand, AngularJS maintains an injector with a list of names and corresponding objects. An entry is added to the injector when a component is created and the object is served whenever it is referenced using the registered name.

Using RequireJS and AngularJS together

The downloadable code included with this article is a simple application containing two pages. It has the following external dependencies:

  • RequireJS
  • jQuery
  • AngularJS
  • Angular Route
  • Angular Resource
  • Angular UI ngGrid

These files should be loaded directly on the page in the order they are listed here. We have five custom script files containing code of the required AngularJS components. Let’s take a look at how these files are defined.

Defining AngularJS Components as RequireJS Modules

Any AngularJS component consists of:

  • A function definition
  • Dependency Injection
  • Registering to an Angular module

Out of the above three tasks, we will perform the first two tasks inside the individual modules, while the third task will be performed in a separate module that is responsible for creating the AngularJS module.

First, let’s define a config block. The config block doesn’t depend on any other blocks, and returns the config function in the end. But, before we load config module inside another module, we need to load everything that is needed for the config block. The following code is contained in config.js:

define([],function(){
  function config($routeProvider) {
    $routeProvider.when('/home', {templateUrl: 'templates/home.html', controller: 'ideasHomeController'})
      .when('/details/:id',{templateUrl:'templates/ideaDetails.html', controller:'ideaDetailsController'})
      .otherwise({redirectTo: '/home'});
  }
  config.$inject=['$routeProvider'];

  return config;
});

Notice the way dependency injection is performed in the above snippet. I used $inject to get the dependencies injected as the config function defined above is a plain JavaScript function. Before closing the module, we return the config function so that it can be sent to the dependent module for further use.

We follow the same approach for defining any other type of Angular component as well, as we don’t have any component specific code in these files. The following snippet shows the definition of a controller:

define([], function() {
  function ideasHomeController($scope, ideasDataSvc) {
    $scope.ideaName = 'Todo List';
    $scope.gridOptions = {
      data: 'ideas',
        columnDefs: [
         {field: 'name', displayName: 'Name'},
         {field: 'technologies', displayName: 'Technologies'},
         {field: 'platform', displayName: 'Platforms'},
         {field: 'status', displayName: 'Status'},
         {field: 'devsNeeded', displayName: 'Vacancies'},
         {field: 'id', displayName: 'View Details', cellTemplate: '<a ng-href="#/details/{{row.getProperty(col.field)}}">View Details</a>'}
        ],
        enableColumnResize: true
        };
    ideasDataSvc.allIdeas().then(function(result){
      $scope.ideas=result;
    });
  }

  ideasHomeController.$inject=['$scope','ideasDataSvc'];

  return ideasHomeController;
});

The Angular module for the application depends on each of the modules defined up to this point. This file gets objects from all other files and hooks them with an AngularJS module. This file may or may not return anything as the result of this file, the Angular module can be referenced from anywhere using angular.module(). The following code block defines an Angular module:

define(['app/config',
  'app/ideasDataSvc',
  'app/ideasHomeController',
  'app/ideaDetailsController'],

  function(config, ideasDataSvc, ideasHomeController, ideaDetailsController){
    var app = angular.module('ideasApp', ['ngRoute','ngResource','ngGrid']);
    app.config(config);
    app.factory('ideasDataSvc',ideasDataSvc);
    app.controller('ideasHomeController', ideasHomeController);
    app.controller('ideaDetailsController',ideaDetailsController);
});

The Angular application cannot be bootstrapped using the ng-app directive as the required script files are loaded asynchronously. The right approach here is to use manual bootstrapping. This has to be done in a special file called main.js. This needs the file defining the Angular module to be loaded first. The code for this file is shown below.

require(['app/ideasModule'],
  function() {
    angular.bootstrap(document, ['ideasApp']);
  }
);

Configuring Grunt to Combine RequireJS Modules

While deploying a JavaScript heavy application, the script files should be combined and minified to optimize download speed of the script files. Tools like Grunt come handy to automate these tasks. It has a number of tasks defined to make any process of front-end deployment easier. It has a task, grunt-contrib-requirejs for combining RequireJS files modules in the right order and then minifying the resulting file. Just like any other grunt task, it can be configured to behave different for each stage of deployment. The following configuration can be used in the demo application:

requirejs: {
  options: {
    paths: {
      'appFiles': './app'
    },
    removeCombined: true,
    out: './app/requirejs/appIdeas-combined.js',
    optimize: 'none',
    name: 'main'
  },
  dev:{
    options:{
      optimize:'none'
    }
  },
  release:{
    options:{
      optimize:'uglify'
    }
  }
}

This configuration would produce an unminified file when Grunt is run with the dev option, and a minified file when grunt is run with the release option.

Conclusion

Managing dependencies becomes challenging when the size of the application grows beyond a certain number of files. Libraries like RequireJS make it easier to define the dependency and not worry about the order of loading of the files. Dependency management is becoming an integral part of the JavaScript applications. AngularJS 2.0 is going to have built-in support for AMD.

Free Guide:

7 Habits of Successful CTOs

"What makes a great CTO?" Engineering skills? Business savvy? An innate tendency to channel a mythical creature (ahem, unicorn)? All of the above? Discover the top traits of the most successful CTOs in this free guide.

  • var = DivisiveCotton

    Are you sure that link to the sample code is correct Ravi? I right clicked “save link as” but it seems to be corrupted

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Divisive, If you click the link, it would take you to a GitHub repository. On the GitHub page, click on View Raw to download the zip file containing code.

  • Leo G.

    You might also like to look at my post to define in a more understandable way, multiple Angular modules with RequireJS. http://leog.me/log/making-sense-of-requirejs-with-angularjs

  • B Shankar J

    With this what we are achieving is instead of including all .js files in the index.html we are including them in ideasModule.js . Am i right? How to load required js file based on template/page/partial i m routing to?

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Shankar, That’s a good question. If you have a set of scripts to be loaded when you are moving to a route, you can add a resolve block to the route and specify all the dependencies in a require block inside it and resolve the promise to pass to the route when all dependencies are loaded.

  • barrypicker

    Your download (source code) does not work. It is missing the folder ‘bower_components’ (meaning all files in the folder are not available). Another incomplete how-to
    #sigh

  • http://sravi-kiran.blogspot.com Ravi Kiran

    Barrypicker,
    Can you try running bower install on the folder before running grunt and update if you are still not able to run the code?

  • Alireza Mirian

    inside the function you pass to define blocks, why don’t you return things like this:

    return [‘$routeProvider’, function config($routeProvider){….}]

    instead of adding [‘$routeProvider’] to $inject property of config function

  • http://sravi-kiran.blogspot.com Ravi Kiran

    Alireza,
    That’s a personal choice. I like it that way. It doesn’t have to do anything with require.js.

  • Cliff Chaney

    Excellent post! I am very interested in implementing this technique, as it overcomes a personal issue I have with angularAMD. I have angularAMD running but it appears to need the controllers to require the app. This is unacceptable to me. However, I apologize that I am a little confused by one point. It seems to me that the above post references a main.js file in two different contexts. There is the main.js that is used by RequireJs to configure paths, etc. Even as a newb to RequireJS, that was familiar. However, towards the end of the post, there’s the main.js that is used to bootstrap the angular app. I’m confused. I thought I was missing something, so I downloaded your sample. It appears to install fine, but when run, all I get is a the “Build Great Apps” heading bar when accessing //localhost:3000. So, SOMETHING is running, but no reference to anything like a “todo list.” At any rate, getting it to run is not a goal for me. But I was unable to find a require.config() call in all of your sample source. Again, I am completely new to RequireJS, so I am likely missing something obvious. Thanks in advance!

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Cliff,

      We are loading main module in index.html and the main module in turn loads app/ideasModule file and this file depends on other files. So, all require.js modules are loaded this way and the components are available for use. But, before loading the main module, there is a reference for appIdeas-combined.js file. This file is generated from grunt task that I explained at the end of the article.

      I didn’t need require.config here to keep the things simpler. Config is useful in bigger apps where you would want to configure base path of scripts or to create shortcuts for some frequently used paths. It is not necessary to have the require.js config block in every app.

  • Cliff Chaney

    Yet another question… Sorry.

    It appears that your approach has the same “weakness” as angularAMD. I say “weakness” because this is purely my opinion. And I believe it may be a fundamental problem with the way that controllers are registered in Angular. Here’s the problem.

    Consider line #7 in your IdeasModule. It is as follows:

    var app = angular.module(‘ideasApp’, [‘ngRoute’,’ngResource’,’ngGrid’]);

    My problem with this is that we need to tell Angular to inject these modules – even though the module has no need of them. The ONLY reason they’re declared is because one of the controllers is going to need it. This sort of prearranged understanding is just the sort of thing I’m trying to avoid by using RequireJS in the first place! And I assume that if one of the controllers uses a module that, in turn, uses another module – I’ll need to declare that one here as well. Really. The whole thing breaks down at that point.

    I’m not throwing stones! This is an excellent article on a great approach that is absolutely a big step in the right direction. But, is there something that I’m missing? Is there some way I could do this that would not require the above declaration and allow the controllers to load/inject what they need?

    Thanks in advance!

    • Cliff Chaney

      I’ve resolved this myself. I’m putting my solution here so that if someone else ends up here with the same problem, I might help them. Though, IMHO, I think you should add this small adjustment to your pattern.

      I thought I might be missing something simple. And it turns out that I was.

      The Angular modules listed in line 7 (mentioned above) should be replaced with ONLY the modules that are required by it (not any that it thinks are required by the controllers). Line #7 in the IdeasModule then becomes:

      var app = angular.module(‘ideasApp’, [‘ideasDataSvc’, ‘ideasHomeController’, ‘ideaDetailsController’]);

      Each controller then simply registers a module that does nothing more than declare the module dependencies.

      As an example, we’d insert a line in the ideasHomeController as follows:

      define([], function() {
      angular.module(‘ideasHomeController’, [‘ngGrid’]); // <– Plus any other modules needed
      function ideasHomeController($scope, ideasDataSvc) {

      That's it. Probably obvious to many. But a small change that IMHO makes the controllers much more reusable and maintainable.

      • http://sravi-kiran.blogspot.com Ravi Kiran

        Cliff,

        Sorry for the slow response. I was on vacation for last 4 days.

        Good to know that you found solution. Your approach is correct. That is the way we create modules in big apps that use a huge number of services, controllers and directives. You can create modules for each component type or for a domain and refer them all in the main module of your app.

  • Shankar

    nice post.. totally worth.
    I am facing two issues .
    1. angularMoment not working is gives Failed to instantiate module angularMoment due to:

    It works without requirejs way.

    2. all pages are working perfectly. although i see one error in console on each refresh.
    I might be issue of order of js files i am loading. anny hints ??
    Uncaught Error: [$injector:modulerr] Failed to instantiate module library due to:
    Error: [$injector:nomod] Module ‘library’ is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
    http://errors.angularjs.org/1.2.9/$injector/nomod?p0=library
    at http://localhost/app/lib/angular/angular.js:79:20
    at http://localhost/app/lib/angular/angular.js:1564:31

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Hi Shankar,

      I am glad that you found this article useful.

      Are you facing these issues in your application? From the error you mentioned, I understand that a dependency is not loaded by the time you are referring the module defined inside that file. Specify that file in the define block or, load it on the page before main block.

      If you are still not able to resolve, I will have to take a look at your code.

  • John Schottler

    Thanks for the article! FYI, the demo code points to ng-grid-2.0.13, but currently when that bower component is installed it brings down ng-grid-2.0.14.

  • http://sravi-kiran.blogspot.com Ravi Kiran

    Yes, I noticed it just now. That is happening because the bower.json refers to ng-grid with ~ before the version number and the script file has version number attached to its name. If you remove it, the demo would work well.

  • http://www.rafaelcamargo.com/ Rafael Camargo

    I didn’t understand one thing. If we concat all js files into ‘appIdeas-combined.js’, what is the advantage of use AMD? All javascript will be downloaded when the app is starting instead balance the charge while user navigates along the app.

    • http://sravi-kiran.blogspot.com Ravi Kiran

      Here the advantage is only sequencing. But, in complex applications you can use AMD to load scripts to be used by an application when the application is getting activated by adding a require statement to route resolve block.

  • Rajkumar

    Great post Ravi! I was actually looking for AMD and DI using AngularJS and RequireJS

  • http://cosmi.nu/ Cosmin Nicula

    Great post!

    One question: Does it work with directives?

    I see the demo is about app.factory() and app.controller(). If you use app.directive() in order to register a directive, and require it ideasHomeController, you’ll get an error “Error: [$injector:unpr] Unknown provider: …..Provider”. Can you also include an example with a directive in the source code on GitHub?

    Thanks!

  • Zeel Shah

    Hi I am getting error that nggrid module not found after installing all dependency using bower,npm..

    Uncaught Error: [$injector:modulerr] Failed to instantiate module ideasApp due to:

    Error: [$injector:modulerr] Failed to instantiate module ngGrid due to:

    Error: [$injector:nomod] Module ‘ngGrid’ is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.

  • lsiden

    Good article, but I am still puzzled why one might need to use RequireJs and Angular together.

    Angular has a 2-phase bootstrap, so it doesn’t matter in what order module definitions get loaded. This will only become an issue when you want to split a large module into several source files. angular.module(‘x’) will throw an exception if ‘x’ hasn’t yet already been registered as angular.module(‘x’, []). If we can avoid splitting modules across files (maybe by having them delegate to sub modules), we will never have to worry about this.

    Nevertheless, the Grunt file from the ngbp boilerplate project accomplishes this by using the convention of naming any file that defines a module as x.module.js and loading them first in index.html. Anything that calls angular.module(‘x’) is not named as *.module.js and gets loaded after.

    If someone can show mea use-case where RequireJs solves a problem for an AngularJS app that can’t be solved otherwise, I’ll be all ears.

    • http://coltureclash.co.uk Simone Trubian

      Good point. I’m using RequireJS to compile my project using r.js. My dev configuration file uses local dependencies, while the production configuration uses CDN for dependendencies like Angular, JQuery and the likes. This is convenient because it allows me to keep the HTML sane loading everything from the RequireJS tag and creating with little effort a compact minified version of the project for production. Is there a way to obtain something similar without using RequireJS?

  • leapoahead

    If you got to put one whole controller into one module, how do you do unit testing? All pieces of a controller are all in that module, but you can only expose controller itself.

  • Chanduram Naidu

    Hi, any one can help me to load the controllers in routing so that all controllers are not loaded in frontpage itself.

    • megaman

      did you finally do that?? I’d like to know too how to do it

  • Daniel Petrov

    Hello! For me require and angular dont work proper for directives. It doesnt assign the controller at the directive. Any idea why?

Recommended
Sponsors
Because We Like You
Free Ebooks!

Grab SitePoint's top 10 web dev and design ebooks, completely free!

Get the latest in JavaScript, once a week, for free.