Modern client-side application development demands modularity. Client-side modularity is required to achieve separation of logic from the UI. To implement this modularity, we usually use an MVC or MVVM framework to manage separate layers (in separate JavaScript files) for Models, Controllers, Modules, Services etc.
AngularJS is a great framework for implementing client-side modularity with MVC support.
However as the application grows, managing JavaScript dependencies on a page becomes a challenge, as you need to load your JavaScript by listing all <script> tags and their dependencies, in a particular order. Any JavaScript developer who has managed multiple <script> tags on a page knows how tedious and error prone the entire exercise is. In many cases, it also affects the performance of the app.
RequireJS is a JavaScript framework that enables asynchronous loading of files and modules and can come in very handy while managing dependencies across different AngularJS components and loading them asynchronously. An alternative to RequireJS is Almond.js, a stripped down version of RequireJS. RequireJS is more popular compared to it.
This article is published from the DNC Magazine for .NET Developers and Architects. Download this magazine from here [Zip PDF] or Subscribe to this magazine for FREE and download all previous and current editions
A simple overview of RequireJS
RequireJS is a JavaScript module and file loader. We start with each JavaScript file, listing the different files it depends on. RequireJS then recognizes these dependencies and loads them in the correct order. Since RequireJS manage dependencies on the client-side, we no longer need to manually refer to all script references on an html page. RequiresJS implements the AMD API (Asynchronous module definition API) and thus inherits all its advantages which includes asynchronous loading of dependencies and implicit resolution of dependencies, amongst other uses.
RequireJS can be used both in browsers as well as with server solutions like Node.js.
Since RequireJS loads files asynchronously, the browser starts its rendering process while waiting for RequireJS to do its work, without any blocking. This improves performance.
Note: If anybody is interested in looking at an implementation of KnockoutJS with RequireJS, please check here https://www.dotnetcurry.com/aspnet-mvc/1180/html-data-grid-aspnet-mvc-using-knockoutjs-jquery-requirejs
In this article, we will be using Bower to install dependencies in this project; in our case - AngularJS and RequireJS. Bower is a package manager for the web. It manages frameworks, libraries etc. needed by the web application. Although I have implemented this application using Visual Studio 2015, you can also implement it using Visual Studio 2013. To learn more about Bower, check this article https://www.dotnetcurry.com/visualstudio/1096/using-grunt-gulp-bower-visual-studio-2013-2015 .
Pre-requisites for the implementation
Here are some pre-requisites before you get started:
Wherever you see Visual Studio, it either means Visual Studio 2013 or Visual Studio 2015. You can choose either, as per availability.
Using Bower to Install Dependencies for the project
Step 1: Open Visual Studio and create an empty ASP.NET project with the name NG_RequireJS as shown in the following image
Step 2: To install necessary dependencies for the project, open Node.js command prompt (Run as Administrator) and follow these steps.
- From the command prompt navigate to the project folder.
- Install Bower for the project
- Initialize Bower. This will add bower.json file to the project. By default, the file is added in the project path which will not be displayed in the Solution explorer. So to view the file, click on Show all files on the toolbar of our Solution Explorer and you will find the bower.json file. Right-click on the file and select Include in project. This needs to be done for all dependencies which will be added in the next steps.
The above image shows bower init command which will ask for options to be added in the bower.json file. Please select options as per your application’s requirements. For this article, we have selected module options for amd, globals and node. Once these options are selected and added, the bower.json file will take the following shape:
The project will be added with bower.json.
- Install AngularJS with the following command: Bower install angularjs –save
- Now install RequireJS with the following command: Bower install requirejs –save
Step 3: Go back to the project in Visual Studio, select Show All Files from the Solution Explorer tool bar and you should now see bower_components include in the project. The project will be displayed as shown in the following image.
bower.js will show dependencies as shown below:
So far so good, we have added the necessary infrastructure for the project. Now we will add the necessary AngularJS components to the project.
Adding AngularJS components
Step 4: In the solution, add a new empty Web API project of the name ServiceProject. We will be using this API as a service provider project for making http calls using Angular’s $http service.
Step 5: In this project, in the Models folder, add a new class file with the following code:
using System.Collections.Generic;
namespace ServiceProject.Models
{
public class Product
{
public int ProdId { get; set; }
public string ProdName { get; set; }
}
public class ProductDatabase : List<Product>
{
public ProductDatabase()
{
Add(new Product() {ProdId=1,ProdName="Laptop" });
Add(new Product() { ProdId = 2, ProdName = "Desktop" });
}
}
}
Step 6: In the Controllers folder, add a new empty Web API Controller of the name ProductInfoAPIController. In this controller, add a Get() method with the following code:
using ServiceProject.Models;
using System.Collections.Generic;
using System.Web.Http;
namespace ServiceProject.Controllers
{
public class ProductInfoAPIController : ApiController
{
public IEnumerable<Product> Get()
{
return new ProductDatabase();
}
}
}
Using RequireJS
Step 7: In the NG_RequireJS project, add a new folder of the name app. In this folder, add the following JavaScript files:
Main.js
require.config({
paths: {
angular: '../bower_components/angular/angular'
},
shim: {
'angular': {
exports:'angular'
}
}
});
require(['app'], function () {
require(['serv', 'ctrl'], function () {
angular.bootstrap(document, ['app']);
});
});
The above script bootstraps RequireJS by calling require.config() function, passing in a configuration object which contains path for the AngularJS framework. Since AngularJS is an external dependency here that is being managed using Bower, we are specifying a relative path to our bower_components directory. To make sure that AngularJS is embedded correctly, we are using RequireJS to assign AngularJS to the global variable angular. This is done using the attribute shim. We are using exports which defines global access to the angular object, so that all JavaScript modules/files can make use of this object.
The above file also defines require() used for loading the necessary modules asynchronously along with the loading of the document (DOM). require() loads the modules - app, serv and ctrl asynchronously.
The following line:
angular.bootstrap(document, ['app']);
..performs the same operation as the ng-app directive does. The reason we are using angular.bootstrap() instead of ng-app is because the files are loaded asynchronously. If we use ng-app, there could be a possibility that when AngularJS loads a module in the ng-app directive, RequireJS may have not yet loaded the module. Hence we use angular.bootstrap() to initialize our app only when RequireJS has finished loading its modules. The first parameter to angular.bootstrap() is the DOM element and the second parameter is the application module that is to be loaded. Here it simply means that the app module will be loaded at document level.
App.js
define(['angular'],function (angular) {
var app = angular.module('app', []);
return app;
});
The above file defines angular module of name ‘app’. The angular object declared through shim is passed to it.
serv.js
define(['app'], function (app) {
app.service('serv', ['$http',function ($http) {
this.getdata = function () {
var resp = $http.get('http://localhost:4737/api/ProductInfoAPI');
return resp;
}
}]);
})
The above code defines angular service of name serv, which makes call to the Web API service and receives a response.
ctrl.js
define(['app'], function (app) {
app.controller('ctrl', ['$scope','serv', function ($scope, serv) {
loaddata();
function loaddata() {
var promise = serv.getdata();
promise.then(function (resp) {
$scope.Products = resp.data;
$scope.Message = "Operation Completed Successfully...";
}, function (err) {
$scope.Message = "Error " + err.status;
})
};
}]);
});
The above file contains code for declaring angular controller. This file has dependency on the angular service. The above controller makes a call to getData() function of the angular service. Once the data is received from the service, it is stored in the Products $scope object.
Step 8: On the project root, add an index.html with the following markup and code:
<!DOCTYPE html>
<html>
<head>
<title>Using RequireJS with Angular.js</title>
<meta charset="utf-8" />
<style type="text/css">
table, td,th {
border:double;
}
</style>
</head>
<body ng-controller="ctrl">
<h1>Using RequireJS with Angular.js</h1>
<table>
<thead>
<tr>
<th>Product Id</th>
<th>Product Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="p in Products">
<td>{{p.ProdId}}</td>
<td>{{p.ProdName}}</td>
</tr>
</tbody>
</table>
<div>{{Message}}</div>
<script src="bower_components/requirejs/require.js" data-main="app/Main.js"></script>
</body>
</html>
In the above file, the following line
<script src="bower_components/requirejs/require.js" data-main="app/Main.js"></script>
..is used to initialize and load RequireJS. The data-main attribute is used to load Main.js, the starting point of our application. This attribute tells require.js to load app/Main.js after require.js has loaded. Main.js configures Angular and other module dependencies.
This is how we eliminate the need to refer to all JavaScript files in an HTML page using RequireJS.
Running the AngularJS and RequireJS Application
Run the application by setting multiple startup projects in Visual Studio, as shown in the following image:
Run the application and the Index.html loads in the browser with the following result:
Right click on the page > View source and you will see all modules listed in order.
Conclusion
RequireJS is an awesome library for managing asynchronous module loading when the logic is segregated in separate JavaScript files. It loads modules and the relevant dependencies in their right order. Make sure you learn this library and use it in a project with multiple JavaScript files!
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn