While working on a SaaS app bookingjoe.com, I was faced with a challenge where the first page, which included large minified JS file, was taking time to load. I tried out several approaches.

AngularJS with RequireJS: There are many generators available on the web to use RequireJS with AngularJS but they do not solve issue of on-demand loading & downloading of AngularJS modules and components registering problem. AngularJS does not allow registration of modules and components after initial bootstrapping of angular app and so RequireJS could only load javascript files but it could not register new angular modules and components on demand. Also after using RequireJS, the main.js file grows to ~500kb. This didn’t work.

AngularJS with ocLazyLoad (https://github.com/ocombe/ocLazyLoad): My requirement was to load first page quickly and load the remaining JS files on demand. I found OCLazyLoad bower component was addressing this problem. It was also light weight. OCLazyLoad registers the modules and components with AngularJS dynamically.

I was still mulling over it but was finally convinced when I read the following thread on stack overflow:http://stackoverflow.com/questions/28222096/angularjs-oclazyload-vs-requirejs
Hat tip to Olive for pointing me in the right direction. Armed with this knowledge, I experimented converting Yeoman based AngularJS project to lazy load it’s components. Since my problem was conversion of existing application to lazy load it’s modules, I created a fresh AngularJS project using Yeoman generator and applied my changes to the generated file.

What I did…

Added a reference of ocLayzLoad and updated JS files reference to load on demand from app.js or .html file of their views.

Updated Gruntfile to uglyfy, renamed file name and updated references in the final .html or .js file.

So let’s see how I did it…

Created a new Yeoman AngularJS project

Generate AngularJS yeoman project

Shell

1

2

3

4

npm install-ggrunt-cli bower yo generator-karma generator-angular

mkdirHelloWorld

cdHelloWorld

yo angular HelloWorld

After creating fresh project, I created 2 more controllers (about.js, contact.js) and 2 services (helloservice.js, contactservice.js).

Added ocLazyLoad and update references of JS files into index.html.
First install the ocLazyLoad bower component

Add ocLazyLoad bower component

Shell

1

bower install oclazyload--save-dev

Quick tip: Need to move “oclazyload: 1.0.9” line in bower.json to “dependencies” section.

To load controller and services js files dynamically, remove their references from index.html. I also needed to update app.js file to remove controller’s name from route entry. Now my app.js route looked like

AngularJS route declaration

JavaScript

1

2

3

4

5

6

7

8

9

10

$routeProvider

.when('/about',{

templateUrl:'views/about.html'

})

.when('/contact',{

templateUrl:'views/contact.html'

})

.otherwise({

redirectTo:'/'

});

Now there are two ways to load controllers and services dynamically.

Load JS file by adding reference of JS in .html file
I added controller and services reference in html view. Here I added required controllers and services js details in about.html. I also provided about.js and helloservie.js file path in html tag “oc-lazy-load”.

Quick tip: ng-controller must come one level below file loading of JS files, because the inner dom will not compile until oc-lazy-load loads specified file and registers modules and components (if required).

Reference of JS in app.js file(contact.html, app.js)
With $ocLazyLoadProvider (app.js), I created list of modules in app.js and specified associated list of files to be loaded with each module. These module definitions do not download file during declaration. They will be downloaded with view loading.

provide path of controllers & services js in App.js

JavaScript

1

2

3

4

5

6

7

$ocLazyLoadProvider.config({

modules:[{

name:'helloWorldApp.contact',

files:['scripts/controllers/contact.js',

'scripts/services/contactservice.js']

}]

});

After adding modules and their dependencies in app.js, I updated html file to use these modules. I also updated the contact.html.

updated html

XHTML

1

2

3

4

5

<div oc-lazy-load="helloWorldApp.contact">

<div ng-controller="ContactCtrl as contact">

Your html goes here

</div>

</div>

Updated Gruntfile.js to Copy and Uglyfy JS files in final build
To avoid loading of controllers and services JS with first page load, I removed references of those JS from index.html. This will create another problem in build. The final build will not have those JS files. I needed to update COPY block in Gruntfile.js to copy remaining controllers and services JS.

Gruntfile.js Copy block

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

copy:{

dist:{

files:[{

expand:true,

dot:true,

cwd:'<%=yeoman.app%>',

dest:'<%=yeoman.dist%>',

src:[

'*.{ico,png,txt}',

'*.html',

'scripts/*/*.js',//Added to copy various folder's JS file to dist.

'views/{,*/}*.html',

'images/{,*/}*.{webp}',

'styles/fonts/{,*/}*.*'

]

},{

expand:true,

cwd:'.tmp/images',

dest:'<%=yeoman.dist%>/images',

src:['generated/*']

},{

expand:true,

cwd:'bower_components/bootstrap/dist',

src:'fonts/*',

dest:'<%=yeoman.dist%>'

}]

},

styles:{

expand:true,

cwd:'<%=yeoman.app%>/styles',

dest:'.tmp/styles/',

src:'{,*/}*.css'

}

}

Quick tip: It is required that all JS files except app.js should be in their respective folders.

Module “filerev” changes JS file name based on hash value(shasum) of file content. When JS files are part of index.html, this operation is done on final minified and concatenated JS file. But here JS files are referred from different JS (app.js) and HTML files. To solve this problem, I updated “usemin” section of Grunt.

Final step in Gruntfile.js is minification and uglification. I updated uglify section (which was commented in original project) to uglify separate JS files.

Gruntfile.js Uglify section

1

2

3

4

5

6

7

8

9

10

uglify:{

dist:{

files:[{

expand:true,

cwd:'<%=yeoman.dist%>/scripts',

src:'*/*.js',

dest:'<%=yeoman.dist%>/scripts'

}]

}

}

Quick tip: Uglification changes AngularJS variables and injects service variable names. So here one has to map the injection of services to it’s variable manually.

Controller file

JavaScript

1

2

3

4

angular.module('helloWorldApp')

.controller('AboutCtrl',['helloService',function(helloService){

this.helloMessage=helloService.SayHello();

}])

Conclusion
There still remained some challenges with ocLazyLoad reference resolutions. I had to solve those issues individually. I am now convinced many Single Page Applications (SPA) can benefit from this enhancement. Would welcome your queries and suggestions. I will be happy to respond.