Search code examples
javascriptjqueryangularjslazy-loadingcontrollers

AngularJS: lazy loading controllers and content


In this simplified scenario, I have two files: index.htm, lazy.htm.

index.htm:

var myApp = angular.module('myApp', []);
myApp.controller('embed',function($scope){
    $scope.embed = 'Embedded Controller';
});                  
<div ng-controller="embed">{{embed}}</div>    
<div ng-include="'lazy.htm'"></div>

lazy.htm

myApp.controller('lazy',function($scope){
    $scope.lazy = 'Lazy Controller';
});
<div ng-controller="lazy">
    {{lazy}}
</div>

The result is an error: "Argument 'lazy' is not a function, got undefined"

Using a function instead

lazy.htm

function lazy($scope) {
    $scope.lazy = 'Lazy Controller';
}
<div ng-controller="lazy">
    {{lazy}}
</div>

This works until version 1.3 beta 14. In beta 15 was removed the global controller functions: https://github.com/angular/angular.js/issues/8296

So now, what is the better way to get angularized contents of lazy.htm dynamically?

UPDATE:

In this article (http://ify.io/lazy-loading-in-angularjs) I found another possible solution. The $controllerProvider allow us to register new controllers after angular bootstrap. Works like a charm. Tested in v1.3.0-beta.18

index.htm:

var myApp = angular.module('myApp', [])
.controller('embed',function($scope){
    $scope.embed = 'Embedded Controller';
})
.config(function($controllerProvider) {
    myApp.cp = $controllerProvider;
});

<div ng-controller="embed">{{embed}}</div>    
<div ng-include="'lazy.htm'"></div>

lazy.htm

myApp.cp.register('lazy',function($scope){
    $scope.lazy = 'Lazy Controller';
});
<div ng-controller="lazy">
    {{lazy}}
</div>

UPDATE 2:

Two other alternatives that works are:

lazy.htm

_app = $('[ng-app]').scope();    
_app.lazy = function($scope) {
    $scope.lazy = 'Lazy Controller';
};

OR

var $rootScope = $('[ng-app]').injector().get('$rootScope');        
$rootScope.lazy = function($scope) {
    $scope.lazy = 'Lazy Controller';
}; 

But I believe these last two examples should not be used in production.


Solution

  • You can also use the jquery with the resolve the $routeProvider

    app.js

    /* Module Creation */
    var app = angular.module ('app', ['ngRoute']);
    
    app.config(['$routeProvider', '$controllerProvider', function($routeProvider, $controllerProvider){
    
    /*Creating a more synthesized form of service of $ controllerProvider.register*/
    app.registerCtrl = $controllerProvider.register;
    
    function loadScript(path) {
      var result = $.Deferred(),
      script = document.createElement("script");
      script.async = "async";
      script.type = "text/javascript";
      script.src = path;
      script.onload = script.onreadystatechange = function (_, isAbort) {
          if (!script.readyState || /loaded|complete/.test(script.readyState)) {
             if (isAbort)
                 result.reject();
             else
                result.resolve();
        }
      };
      script.onerror = function () { result.reject(); };
      document.querySelector("head").appendChild(script);
      return result.promise();
    }
    
    function loader(arrayName){
    
        return {
          load: function($q){
                    var deferred = $q.defer(),
                    map = arrayName.map(function(name) {
                        return loadScript('js/controllers/'+name+".js");
                    });
    
                    $q.all(map).then(function(r){
                        deferred.resolve();
                    });
    
                    return deferred.promise;
            }
        };
    }
    
    $routeProvider  
        .when('/', {
            templateUrl: 'views/foo.html',
            resolve: loader(['foo'])
        })
        .when('/bar',{
            templateUrl: 'views/bar.html',
            controller: 'BarCtrl',
            resolve: loader(['bar'])
        })
        .otherwise({
            redirectTo: document.location.pathname
        });
    }]);
    

    /views/foo.html

    <section ng-controller='FooCtrl'>
        {{text}}
    </section>
    

    js/controllers/foo.js

    /*Here we use the synthesized version of $controllerProvider.register 
    to register the controller in view*/
    app.registerCtrl('FooCtrl',function($scope){
        $scope.text = 'Test';
    });
    

    /views/bar.html

    <section>
        {{text2}}
    </section>
    

    js/controllers/bar.js

    app.registerCtrl('BarCtrl',function($scope){
        $scope.text2 = 'Test';
    });