RequireJS with AngularJS

What is RequireJS?

RequireJS is a JavaScript file loader or module loader. As the name suggests, requirejs helps us to load the JavaScript files, modules, libraries, or plugins (along with their dependencies) only when we require them.

Why RequireJS?

Normal web applications which use MVC patterns of coding in the front end are working in such a way that:-

We have to specify all the JavaScript files, plugins, or libraries in the index.html of the application.

The order of plugins, libraries, and custom files has to be maintained because library functions inside the custom files will not work if libraries are mentioned after custom files.

Finally, the very first loading of the application results in downloading all the JavaScript files needed for the whole application into the browser.

These facts clearly imply that the index.html is getting uglier as the application grows. You can neither blindly guess the order of the plugins, libraries, or custom files nor place them in the index.html at random. And the very first loading of the application is going to be time-consuming, even if the launching page – usually the login page – doesn’t require most of the custom files, libraries, or plugins which are getting loaded.

Here comes the relevance of RequireJS.

How RequireJS?

Allows us to keep the index.html page clean just by adding only one script tag as follows:-
<script data-main=”scripts/main” src=”scripts/require.js”></script>

During the development, you will be having more script files, but you don’t have to mention any of them inside the index.html page, but the main.js specified at the data-main of this script tag will include them. Hence the index.html page looks good.

Allows us to provide libraries and plugins which are needed for or dependent on a specific custom script or library in any order you want. Allows us to load only those modules, files, plugins, and libraries which are needed for the current scope of the application.

RequireJS Concepts

Driving fast into the concepts of RequireJS; there are four dependency types, four great features, and three simple APIs.

The four Dependency Types are as follows:-

  • Load Dependency – Determines which class or files needed to be loaded in what order.
  • Contractor Dependency – Determines what parameters or arguments you need to pass before constructing an instance of a class.
  • Runtime Dependency – Determines what functions and utilities you need during pre-instantiation or post-instantiation.
  • Module Dependency – Determines the dependency where one module depends on another module and is a special one because it is angular kind, hence to apply with AngularJS.

The four great features are as follows:-

  • Package Dependency Manager – This allows us to provide dependencies in their order.
  • Injector – This allows us to inject classes or dependencies into the module. It doesn’t mean injectors like AngularJS injectors (they inject instances) but injects classes.
  • NB: AngularJS injects instances whereas requireJS injects classes.
  • JavaScript File Loader – This allows us to load JavaScript files only when they are needed.
  • Concatenator – It is a concatenator, uglifier or minifier.
    Before discussing those three APIs, take a look at the application of RequireJS in AngularJS.

By clubbing above mentioned 4 dependency types and 4 great features with AngularJS, a nice scenario has evolved that, RequireJS can manage load dependency and runtime dependency whereas AngularJS can manage constructor dependency and module dependency. Hence the outcome is that; confusion about the order of dependencies vanishes, and anxiety about the initial load is resolved.

Back to the essentials of RequireJS, there are 3 simple APIs; define(), require(), and config().

define():- This allows us to create an AMD (Asynchronous Module Definition). This definition will return a value, which will be cached into the internal registry of RequireJS. This returned value is provided by the ready handler of the same AMD. The ready handler will return the value only after the dependencies specified inside the AMD are resolved.

Sample code:-

define(“file3”, [“file1”, “file2”, function( file1, file2){

return{

//code depending on file1 and file2 goes here

}

}]);

So the consolidation is that an AMD will be a useful one, only after all the dependencies are resolved and we can specify these dependencies in any order we want. If there are no dependencies, the ready handler returns the AMD value immediately. The key thing is that this returned value is about to inject into other AMDs as their dependencies. That is there exists a tree of dependencies.

RequireJS with AngularJS

We can use AMD1 only after a chain of dependencies is resolved. So the global define() builds a tree of dependencies we are mentioning. Then all those ready handlers are fired. They build a flat registry of values stored by the dependency IDs. These values are usually references or classes. Hence define() allows us to build a dependency tree.

But nothing happens until we say, we want to start the trigger! require() helps us to do this.

require():- Acts as root or initialization of the dependency tree. Values specified inside require() will be having dependencies on many other values and so on. require() starts the cascade of dependency tree checks and script file loading.
Sample code:-

require([“modulename”]);

config():- Allows us to configure the location and path to the source files. It also provides aliases for source path.
Sample code :-

require.config ({

paths : {

‘filepath1’ : bowerPath + ‘file1/file1.min’

}

});

Better use the bower tool for downloading the plugins, libraries, and CDN files. Because bower takes care of hunting, finding, downloading, and saving the JavaScript stuff we are looking for.

What is the advantage of using bower than mentioning the libraries manually?

Normally we specified the JavaScript files by the CDN/source path within the application. What about some changes in the library such as we need the updated version of the same library? Of course, we will have to change the version specification of CDN or download the new version and replace it within the application, build the application again and deploy it. For a deployed application it doesn’t sound good. Think about updating the changes or version automatically. Bower helps us to do this.

Bower is a package manager. To utilize bower, prerequisites for the system are npm and bower. Bower has a config file as bower.json where we mention a specific version, version of a specific series, latest version, or latest of the library. Bower has a default bower_components folder where all the libraries mentioned in the bower.json are getting downloaded automatically into this folder. We just need to mention this path properly in your main.js only once. Bower will take care of any kind of updation, version changes up to date. We don’t have to change them manually.

Sample code:-

{

“name”: “project_name”,

“version”: “1.0”,

“description”: “project_description”,

“license”: “MIT”,

“ignore”: [

“**/.*”,

“node_modules”,

“bower_components”,

“test”,

“tests”

],

“dependencies”: {

“requirejs”: “~2.1.18”,

“angular”: “1.3.8”,

}

“authors”: [

“manager”

]

}

So what would be the out come of all these discussions?

Demo application structure :-

Demo

index.html
partials
css
bower.json
js
main.js
bootsrtap.js
bower_components
require
angular
angular-route
scripts
app.js
controllers
loginController
homeController
Demo application code :-

index.html

<!DOCTYPE html>

<html xmlns=”http://www.w3.org/1999/xhtml”>

<meta charset=”utf-8″>

<head>

<link rel=”stylesheet” type=”text/css” href=”css/style.css”>

<script data-main=”js/main.js” src=”js/bower_components/require”></script>

</head>

<body>

<div ng-view class=”container”></div>

</body>

</html>

main.js

var baseUrl = “../../demo2/js”;

require.config({

baseUrl: location.href + ‘/../’, // alias libraries paths

paths: {

‘angular’ : baseUrl+’/bower_components/angular’,

‘ngRoute’ : baseUrl+’/bower_components/angular-route’,

‘app’ : ‘scripts/app’,

‘loginCtrl’: ‘scripts/controllers/loginController’,

‘homeCtrl’ : ‘scripts/controllers/homeController’,

},

shim: {

‘angular’: {

exports: ‘angular’

},

‘ngRoute’: {

deps: [‘angular’]

},

},

deps: [‘./bootstrap’] // kick start application

});

bootstrap.js :-

define([‘require’,’app’], function (require) {

‘use strict’;

angular.bootstrap(document, [‘webapp’]);

});

app.js :-

define([‘angular’, ‘ngRoute’], function () {

var app = angular.module(“webapp”, [‘ngRoute’]);

app.config([‘$routeProvider’, ‘$controllerProvider’, ‘$provide’,

function ($routeProvider, $controllerProvider, $provide) {

app.register = {

controller: $controllerProvider.register,

factory: $provide.factory

};

function resolveController(names) {

return {

load: [‘$q’, ‘$rootScope’, function ($q, $rootScope) {

var defer = $q.defer();

require(names, function () {

defer.resolve();

$rootScope.$apply();

});

return defer.promise;

}]

}

}

$routeProvider

.when(“/login”, {

templateUrl: “partials/login.html”,

controller: “loginCtrl”,

resolve: resolveController([“loginCtrl”])

})

.when(“/home”, {

templateUrl: “partials/home.html”,

controller: “homeCtrl”,

resolve: resolveController([“homeCtrl”])

})

.otherwise({redirectTo: ‘/login’})

}]);

return app;

});

loginController.js :-

define([‘app’], function (app) {

app.register.controller(‘loginCtrl’, [‘$scope’, ‘$location’, function ($scope, $location) {

$scope.title = “Login”;

}])

});

Have questions? Contact the software technology experts at InApp to learn more.