Lazy Loading Angular (<= 1.3) Controllers

As your angular application grows, you'll quickly look for ways to distribute logic into manageable portions. You could (and should) simply produce js files dedicated to directives, controllers, and app separately, then use grunt to create your production file. But, you can do one better than that. You can simply not load entire sections of the site the user never visits. Through the use of require.js and angular's ui-router you can lazy load page controllers on demand, just like the template files.

File Breakdown

To make this work you'll need the following setup:

index.html

  1.   ...
  2. </head>
  3.   <div id="wrap">
  4.     ...
  5.     <main id="main" ng-include="Page.mainUrl">
  6.         <div ng-view autoscroll="true" class="slide"></div>
  7.     </main>
  8.     ...
  9.   </div>
  10.   <script type="text/javascript" src="/assets/require.min.js"></script>
  11.   <script type="text/javascript" src="/app/core/app.js"></script>
  12. </body>

app.js

  1. // declare app and modules
  2. var app = angular.module('main', [
  3.   'ngRoute'
  4.   ...
  5. ]);
  6.  
  7. // Prepare configuration to lazy load items based on route
  8. app.config([
  9.   '$routeProvider',
  10.   '$controllerProvider',
  11.   '$compileProvider',
  12.   '$filterProvider',
  13.   '$provide',
  14.   ...
  15.   function(
  16.     $routeProvider,
  17.     $controllerProvider,
  18.     $compileProvider,
  19.     $filterProvider,
  20.    $provide,
  21.    ...
  22. ) {
  23.   ...
  24.   // save references to the providers so we can lazy load items later
  25.   app.lazy = {
  26.     controller: $controllerProvider.register,
  27.     directive: $compileProvider.directive,
  28.     filter: $filterProvider.register,
  29.     factory: $provide.factory,
  30.     service: $provide.service,
  31.   };
  32.   /* Routes */
  33.   $routeProvider
  34.     // Standard routing
  35.     .when('/', {
  36.       templateUrl : 'app/core/templates/home.html',
  37.       controller  : 'mainCtrl'
  38.     })
  39.     // Lazy loading routing
  40.     .when('/maintenance', {
  41.       templateUrl : 'app/maintenance/index.html',
  42.       controller  : 'maintenanceCtrl',
  43.       resolve: {
  44.         load: ['$q', '$rootScope', function ($q, $rootScope) {
  45.           var deferred = $q.defer();
  46.           require ([
  47.             'app/maintenance/controller', // (will look for controller.js)
  48.           ], function () {
  49.              $rootScope.$apply(function () { deferred.resolve(); });
  50.           });
  51.           return deferred.promise;
  52.         }]
  53.       }
  54.     });
  55.   ...
  56. }]);

Lazy controller setup

The new controllers are pretty much exactly the same as normal controllers. The only difference is the initial controller loading that looks like this:

  1. app.lazy.controller('maintenanceCtrl', function(
  2.   $scope,
  3.   ...
  4. ){
  5.   ...
  6. });
Tags: 

Comments

Need a working version of angular 1.4 using a lazy loading pattern for controllers? See https://github.com/tnajdek/angular-requirejs-seed. I'll be making a fork to preload some of my own favorite tweaks, but it's the most basic solution that covers what you need.

The lazy loading pattern they use:
1. In the initial app.js file, require loads 'view2/view2'.js
2. view2.js defines an additional route through $routeProvider that uses a controller in that file 'View2Ctrl'.
3. View2Ctrl is a lightweight shell that then uses requirejs to load view2/ctrl2.
4. ctrl2.js defines a more standard controller, with the addition of $scope.apply() at the end.

This should give you an illustration of how to register entire sections of the site via initial require patterns that register routes and shell lazy loaders using step 1. The only thing that worries me about this seed is the lack of Controller As pattern. Angular 2.0 is going to require it.

how can we test this snippet using jasmine

I'm not quite sure I understand your difficulty. Without more to go on, maybe the best thing I could do is just link you along to my other post on setting up Protractor for automated end to end testing. Protractor is the defacto standard for Angular, as it handles waiting for processing hooks for you through a standardized means. You might be able to get up and running with it quickly. My own loading methods here work just fine in it.

Check out Automated end to end testing in angular

I'm Not able to write unit test cases of controller with this syntax "app.lazy.controller('Controller name',[])". It gives exception "Cannot read property 'controller' of undefined".

I would wonder what app is for you in your unit tests? It sounds like you're not getting the same definition. I explicitly set app.lazy = { controller: $controllerProvider.register, ... } in app.config. Seeing it undefined leans me to think you're running a modified app entry point, or app all together.

Add new comment

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.