AngularJS dynamic load Controller

Posted by bastienvans on Sat, 04 Apr 2020 17:24:02 +0200

We encapsulate the implementation of dynamic loading Controller method into a general module, and name the module ngCommon.

(function (angular) {'use strict';
    var CommonApp = angular.module('ngCommon');
    ...
})(angular);

Next, we implement a dynamic js loading method $require.

/* Record the loaded js */
var loaded = {};
/* Check whether to load */
var checkLoaded = function (url) {
    return !url || !angular.isString(url) || loaded[url];
};

CommonApp.factory('$require', ['$document', '$q', '$rootScope', function ($document, $q, $rootScope) {
    return function (url) {
        var script = null;
        var onload = null;
        var doc = $document[0];
        var body = doc.body;
        var deferred = $q.defer();
        if (checkLoaded(url)) {
            deferred.resolve();
        } else {
            script = doc.createElement('script');
            onload = function (info) {
                if (info === 1) {
                    deferred.reject();
                } else {
                    loaded[url] = 1;
                    /* AngularJS < 1.2.x Please use $timeout */
                    $rootScope.$evalAsync(function () {
                        deferred.resolve();
                    });
                }
                script.onload = script.onerror = null;
                body.removeChild(script);
                script = null;
            };
            script.onload = onload;
            script.onerror = function () {
                onload(1);
            };
            script.async = true;
            script.src = url;
            body.appendChild(script);
        }
        return deferred.promise;
    };
}]);

Then we focus on the dynamic loading of Controller through the resolve function of $routeProvider route.

CommonApp.provider('$routeResolver', function () {
    this.$get = function () {
        return this;
    };
    this.route = function (routeCnf) {
        var controller = routeCnf.controller;
        var controllerUrl = routeCnf.controllerUrl;
        if (controllerUrl) {
            routeCnf.reloadOnSearch = routeCnf.reloadOnSearch || false;
            routeCnf.resolve = {
                load: ['$route', '$require', 'ControllerChecker',
                    function ($route, $require, ControllerChecker) {
                        var controllerName = angular.isFunction(controller) ? controller($route.current.params) : controller;
                        var url = angular.isFunction(controllerUrl) ? controllerUrl($route.current.params) : controllerUrl;
                        if (checkLoaded(url) || (controllerName && ControllerChecker.exists(controllerName))) {
                            loaded[url] = true;
                            return;
                        }
                        return $require(url);
                }]
            };
        }
        return routeCnf;
    };
})

Look at the code above, a Controller checker is also injected to check whether the current Controller has been registered. If not, we will load the relevant js to register a new Controller. The code is as follows:

CommonApp.service('ControllerChecker', ['$controller', function ($controller) {
    return {
        exists: function (controllerName) {
            if (angular.isFunction(window[controllerName])) {
                return true;
            }
            try {
                $controller(controllerName, {}, true);
                return true;
            } catch (e) {
                return false;
            }
        }
    };
}]);

Finally, let's add a way to annotate dynamic volumes.

CommonApp.setupRegister = function (module) {
    module.config([
        '$controllerProvider',
        '$compileProvider',
        '$filterProvider',
        '$provide',
        function ($controllerProvider, $compileProvider, $filterProvider, $provide) {
            module.register = {
                controller: $controllerProvider.register,
                directive: $compileProvider.directive,
                filter: $filterProvider.register,
                factory: $provide.factory,
                service: $provide.service,
                value: $provide.value,
                constant: $provide.constant
            };
        }
    ]);
};

This is almost finished. How to use it?

var DemoApp = angular.module('DemoApp',['ngRoute','ngCommon']);
/* Call dynamic registration method to add dynamic registration method for current module */
angular.module('ngCommon').setupRegister(DemoApp);
DemoApp.config(['$routeProvider', '$routeResolverProvider', function ($routeProvider, $routeResolverProvider) {
    var route = $routeResolverProvider.route;
    $routeProvider.when('/index', route({
        templateUrl: './view/index.html'),
        controller: 'IndexController', /* It's stated here that the controller doesn't need to state ng controller in html anymore */
        controllerUrl: './controller/index.js')
    }))
    .otherwise('/index');

/* ./controller/index.js */
DemoApp.register.controller('IndexController', ['$scope', '$require', function($scope, $require) {
    ...
    /* Loading a js file dynamically */
    $require(url).then(function () {
        ...
    });
}]);

Topics: angular AngularJS