Join the Stack Overflow Community
Stack Overflow is a community of 6.4 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

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.

share|improve this question
1  
I also found requirejs hard to use with other libraries and vice-versa. That is why I created a library which is much easier to use and is tested with angular. There is a demo application at the bottom: gngeorgiev.github.io/Modulerr.js You can also combine all scripts into one without the dependency to Modulerr.js – Georgi-it Aug 10 '14 at 21:29
    
This question gave me one of the most beautiful moments in my life. Through simple usage of $controllerProvider, now I can have self-contained piece of client-side codes. That is, HTML + JS in one file. – Saeed Neamati Feb 15 '15 at 11:32
    
You can use requireJs and ocLazyload to load the files and inject modules dynamically.. Refer the article codeproject.com/Articles/1039826/… – Jeeva Jsb Nov 2 '15 at 3:59
up vote 13 down vote accepted

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';
});
share|improve this answer
1  
This does not work always - sometimes getScript executes after the view is rendered. /*Here is the magic, the jquery getScript will take the script in the directory that I want before rendering the view*/ – Amit Mar 3 '15 at 13:44
    
Hi, thanks for watching, I made a new version, try now – André Betiolo Oct 13 '15 at 14:41

////JConfig file--------

window.angularApp.config(function ($routeProvider,$controllerProvider,$compileProvider,$provide, azMessages) {

$routeProvider.when('/login', {
             resolve: {
                 load: ['$q', '$rootScope', function ($q, $rootScope) {
                     var deferred = $q.defer();
                     require([

                         //load required Js file here

                ], function () {
                    $rootScope.$apply(function () {
                        deferred.resolve();
                    });
                });
                     return deferred.promise;
                 } ]
             }
         });


  $routeProvider.otherwise({ redirectTo: '/login' });

    window.angularApp.components = {
        controller: $controllerProvider.register,
        service: $provide.service,
        directive: $compileProvider.directive
    }

//contoller declaration

angularApp.components.controller('DiscussionController',[function(){

}]);
share|improve this answer

At first I utilized André Betiolo's answer. However, it does not always work becasue the ajax loading is non-blocking causing the view to sometimes request the controller prior to the script being loaded.

As a solution i forced the function not to return until all scripts successfully loaded. This is kind of hackish but makes sure the loads are successful prior to completing the resolve. It also allows for loading of multiple controllers.

app.js

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;

    //jquery to dynamically include controllers as needed
    function controllers(controllers){
        var numLoaded = 0;
        for (i = 0; i < controllers.length; i++) {
            $.ajaxSetup({async:false});
            $.getScript('js/controllers/' + controllers[i] + '.js').success(function(){
                numLoaded++;
                if (numLoaded == controllers.length) {
                    return true; //only return after all scripts are loaded, this is blocking, and will fail if all scripts aren't loaded.
                }
            });
        }
    }

    $routeProvider
        .when('/', {
            templateUrl: 'views/foo.html',
            resolve: {
                load: function () {
                    controllers(['foo'])
                }
            }
        })
        .when('/bar',{
            templateUrl: 'views/bar.html',
            controller: 'BarCtrl',
            resolve: {
                load: function () {
                    controllers(['bar','foo']) //you can load multiple controller files
                }
            }
        })
        .otherwise({
            redirectTo: document.location.pathname
        });
}]);

/views/foo.html

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

/views/bar.html

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

/controllers/bar.js

app.registerCtrl('BarCtrl',function($scope){
    $scope.text2 = 'Test';
});
share|improve this answer
    
I made a new version, try now – André Betiolo Oct 13 '15 at 14:41

You can have pure AngularJS lazy loading.

Create "LazyService":

var ng = angular.module('app');

ng.factory('lazyService', [ '$http', function($http) {
    var jsPath = 'js/${ name }.js';
    var promisesCache = {};

    return {
        loadScript: function(name) {
            var path = jsPath.replace('${ name }', name);
            var promise = promisesCache[name];
            if (!promise) {
                promise = $http.get(path);
                promisesCache[name] = promise;

                return promise.then(function(result) {
                    eval(result.data);
                    console.info('Loaded: ' + path);
                });
            }

            return promise;
        }
    }
}]);

Then, define your config:

var ng = angular.module('app', [ 'ngRoute' ]);

ng.config([ '$routeProvider', '$controllerProvider', '$provide', function($routeProvider, $controllerProvider, $provide) {
    // Lazy loading
    ng.lazy = {
        controller: $controllerProvider.register,
        //directive: $compileProvider.directive,
        //filter: $filterProvider.register,
        factory: $provide.factory,
        service: $provide.service
    }

    $routeProvider
    .when('/', {
        templateUrl: 'view/home.html'
    })
    .when('/vendor', {
        templateUrl: 'view/vendor.html',
        resolve: {
            svc: [ 'lazyService', function(lazyService) {
                return lazyService.loadScript('services/vendor');
            }],
            ctrl: [ 'lazyService', function(lazyService) {
                return lazyService.loadScript('controllers/vendor');
            }]
        }
    });
. . .

On "js/services/vendor.js", create service as:

var ng = angular.module('app');
ng.lazy.service('vendorService', [ function() {
. . .

On "js/controllers/vendor.js", create controller as:

var ng = angular.module('app');
ng.lazy.controller('vendorController', [ function() {
. . .

The "resolve" property on when defines which promises should be resolved before route loads.

share|improve this answer
    
For complete version see: gist.github.com/skarllot/14905d5c19f1fefc4ca2 – Skarllot Aug 21 '15 at 18:46

The best way to do what you are asking is to instead use a directive and tie the controller and template together that way so its bound at the appropriate time. Currently, the binding it not happening in lazy.htm at the right time unless you declare a global function as you've shown in your second example.

share|improve this answer

Ideally - Angular will force you to separate HTML and JS as in newer versions this may be enforced more often.

You may have to use requireJS http://solutionoptimist.com/2013/09/30/requirejs-angularjs-dependency-injection/

For the sake of trick can you try

ng-controller-controller="'lazy'"

or

In HTML

ng-controller-controller="myObject.controller"

Somewhere inject

$scope.myObject.controller = $controller('lazy', {$scope: $scope})
share|improve this answer

Try this ARI plugin for Angular JS. It helps you to lazy load the controller scripts on demand.

share|improve this answer

You also can use Directives to load your controller!

A example here:

https://gist.github.com/raphaelluchini/53d08ed1331e47aa6a87

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.