Inject module dynamically, only if required

后端 未结 6 1985
滥情空心
滥情空心 2020-11-30 19:52

I\'m using Require.js in combination with Angular.js.

Some controllers need huge external dependencies which others don\'t need, for example, FirstController

相关标签:
6条回答
  • 2020-11-30 20:23

    Attention: use the solution by Nikos Paraskevopoulos, as it's more reliable (I'm using it), and has way more examples.


    Okay, I have finally found out how to achieve this with a brief help with this answer.

    As I said in my question, this has come to be a very hacky way. It envolves applying each function in the _invokeQueue array of the depended module in the context of the app module.

    It's something like this (pay more attention in the moduleExtender function please):

    define([ "angular" ], function( angular ) {
        // Returns a angular module, searching for its name, if it's a string
        function get( name ) {
            if ( typeof name === "string" ) {
                return angular.module( name );
            }
    
            return name;
        };
    
        var moduleExtender = function( sourceModule ) {
            var modules = Array.prototype.slice.call( arguments );
    
            // Take sourceModule out of the array
            modules.shift();
    
            // Parse the source module
            sourceModule = get( sourceModule );
            if ( !sourceModule._amdDecorated ) {
                throw new Error( "Can't extend a module which hasn't been decorated." );
            }
    
            // Merge all modules into the source module
            modules.forEach(function( module ) {
                module = get( module );
                module._invokeQueue.reverse().forEach(function( call ) {
                    // call is in format [ provider, function, args ]
                    var provider = sourceModule._lazyProviders[ call[ 0 ] ];
    
                    // Same as for example $controllerProvider.register("Ctrl", function() { ... })
                    provider && provider[ call[ 1 ] ].apply( provider, call[ 2 ] );
                });
            });
        };
    
        var moduleDecorator = function( module ) {
            module = get( module );
            module.extend = moduleExtender.bind( null, module );
    
            // Add config to decorate with lazy providers
            module.config([
                "$compileProvider",
                "$controllerProvider",
                "$filterProvider",
                "$provide",
                function( $compileProvider, $controllerProvider, $filterProvider, $provide ) {
                    module._lazyProviders = {
                        $compileProvider: $compileProvider,
                        $controllerProvider: $controllerProvider,
                        $filterProvider: $filterProvider,
                        $provide: $provide
                    };
    
                    module.lazy = {
                        // ...controller, directive, etc, all functions to define something in angular are here, just like the project mentioned in the question
                    };
                    module._amdDecorated = true;
                }
            ]);
        };
    
        // Tadaaa, all done!
        return {
            decorate: moduleDecorator
        };
    });
    

    After this has been done, I just need, for example, to do this:

    app.extend( "ui.codemirror" ); // ui.codemirror module will now be available in my application
    app.controller( "FirstController", [ ..., function() { });
    
    0 讨论(0)
  • 2020-11-30 20:25

    The problem with existing lazy load techniques is that they do things which I want to do by myself.

    For example, using requirejs, I would like to just call:

    require(['tinymce', function() {
       // here I would like to just have tinymce module loaded and working
    });
    

    However it doesn't work in that way. Why? As I understand, AngularJS just marks the module as 'to be loaded in the future', and if, for example, I will wait a bit, it will work - I will be able to use it. So in the function above I would like to call some function like loadPendingModules();

    In my project I created simple provider ('lazyLoad') which does exactly this thing and nothing more, so now, if I need to have some module completely loaded, I can do the following:

    myApp.controller('myController', ['$scope', 'lazyLoad', function($scope, lazyLoad) {
    
        // ........
    
        $scope.onMyButtonClicked = function() {
    
            require(['tinymce', function() {
                lazyLoad.loadModules();
    
                // and here I can work with the modules as they are completely loaded
            }]);
        };
    
        // ........
    
    });
    

    here is link to the source file (MPL license): https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js

    0 讨论(0)
  • 2020-11-30 20:30

    I am sending you sample code. It is working fine for me. So please check this:

    var myapp = angular.module('myapp', ['ngRoute']);
    
    /* Module Creation */
    var app = angular.module('app', ['ngRoute']);
    
    app.config(['$routeProvider', '$controllerProvider', function ($routeProvider, $controllerProvider) {
    
    app.register = {
        controller: $controllerProvider.register,
        //directive: $compileProvider.directive,
        //filter: $filterProvider.register,
        //factory: $provide.factory,
        //service: $provide.service
    };
    
    
    //    so I keep a reference from when I ran my module config
    function registerController(moduleName, controllerName) {
        // Here I cannot get the controller function directly so I
        // need to loop through the module's _invokeQueue to get it
        var queue = angular.module(moduleName)._invokeQueue;
        for (var i = 0; i < queue.length; i++) {
            var call = queue[i];
            if (call[0] == "$controllerProvider" &&
               call[1] == "register" &&
               call[2][0] == controllerName) {
                app.register.controller(controllerName, call[2][1]);
            }
        }
    }
    
    
    var tt = {
        loadScript:
    function (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(".shubham").appendChild(script);
        return result.promise();
    }
    }
    
    function stripScripts(s) {
        var div = document.querySelector(".shubham");
        div.innerHTML = s;
        var scripts = div.getElementsByTagName('script');
        var i = scripts.length;
        while (i--) {
            scripts[i].parentNode.removeChild(scripts[i]);
        }
        return div.innerHTML;
    }
    
    
    function loader(arrayName) {
        return {
            load: function ($q) {
                stripScripts(''); // This Function Remove javascript from Local
                var deferred = $q.defer(),
                map = arrayName.map(function (obj) {
                    return tt.loadScript(obj.path)
                    .then(function () {
                        registerController(obj.module, obj.controller);
                    })
                });
    
                $q.all(map).then(function (r) {
                    deferred.resolve();
                });
                return deferred.promise;
            }
        };
    };
    
    
    
    $routeProvider
        .when('/first', {
            templateUrl: '/Views/foo.html',
            resolve: loader([{ controller: 'FirstController', path: '/MyScripts/FirstController.js', module: 'app' },
                { controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' }])
        })
    
        .when('/second', {
            templateUrl: '/Views/bar.html',
            resolve: loader([{ controller: 'SecondController', path: '/MyScripts/SecondController.js', module: 'app' },
            { controller: 'A', path: '/MyScripts/anotherModuleController.js', module: 'myapp' }])
        })
        .otherwise({
            redirectTo: document.location.pathname
            });
    }])
    

    And in HTML Page:

    <body ng-app="app">
    
    <div class="container example">
        <!--ng-controller="testController"-->
    
        <h3>Hello</h3>
    
        <table>
            <tr>
                <td><a href="#/first">First Page </a></td>
                <td><a href="#/second">Second Page</a></td>
            </tr>
        </table>
    
    
    
    
            <div id="ng-view" class="wrapper_inside" ng-view>
            </div>
        <div class="shubham">
        </div>
    </div>
    

    0 讨论(0)
  • 2020-11-30 20:31

    I have been trying to mix requirejs+Angular for some time now. I published a little project in Github (angular-require-lazy) with my effort so far, since the scope is too large for inline code or fiddles. The project demonstrates the following points:

    • AngularJS modules are lazy loaded.
    • Directives can be lazy loaded too.
    • There is a "module" discovery and metadata mechanism (see my other pet project: require-lazy)
    • The application is split into bundles automatically (i.e. building with r.js works)

    How is it done:

    • The providers (e.g. $controllerProvider, $compileProvider) are captured from a config function (technique I first saw in angularjs-requirejs-lazy-controllers).
    • After bootstraping, angular is replaced by our own wrapper that can handle lazy loaded modules.
    • The injector is captured and provided as a promise.
    • AMD modules can be converted to Angular modules.

    This implementation satisfies your needs: it can lazy-load Angular modules (at least the ng-grid I am using), is definitely hackish :) and does not modify external libraries.

    Comments/opinions are more than welcome.


    (EDIT) The differentiation of this solution from others is that it does not do dynamic require() calls, thus can be built with r.js (and my require-lazy project). Other than that the ideas are more or less convergent across the various solutions.

    Good luck to all!

    0 讨论(0)
  • 2020-11-30 20:36

    There is a directive that will do this:

    https://github.com/AndyGrom/loadOnDemand

    example:

    <div load-on-demand="'module_name'"></div>
    
    0 讨论(0)
  • 2020-11-30 20:50

    The key to this is that any modules your app module depends on also needs to be a lazy loading module as well. This is because the provider and instance caches that angular uses for its $injector service are private and they do not expose a method to register new modules after initialization is completed.

    So the 'hacky' way to do this would be to edit each of the modules you wish to lazy load to require a lazy loading module object (In the example you linked, the module is located in the file 'appModules.js'), then edit each of the controller, directive, factory etc calls to use app.lazy.{same call} instead.

    After that, you can continue to follow the sample project you've linked to by looking at how app routes are lazily loaded (the 'appRoutes.js' file shows how to do this).

    Not too sure if this helps, but good luck.

    0 讨论(0)
提交回复
热议问题