仅在需要时动态注入模块

时间:2013-09-03 12:08:02

标签: angularjs requirejs

我正在将Require.js与Angular.js结合使用。

某些控制器需要巨大的外部依赖关系而其他人不需要,例如,FirstController需要Angular UI Codemirror。这是额外的135 kb,至少:

require([
  "angular",
  "angular.ui.codemirror" // requires codemirror itself
], function(angular) {
  angular.module("app", [ ..., "ui.codemirror" ]).controller("FirstController", [ ... ]);
});

我不希望每次加载页面时都必须包含指令和Codemirror lib,以使Angular满意。
这就是我现在正在加载控制器的原因,只有当路线被击中时like what's done here

然而,当我需要像

这样的东西时
define([
  "app",
  "angular.ui.codemirror"
], function(app) {
  // ui-codemirror directive MUST be available to the view of this controller as of now
  app.lazy.controller("FirstController", [
    "$scope",
    function($scope) {
      // ...
    }
  ]);
});

我如何告诉Angular在app模块中注入ui.codemirror模块(或任何其他模块)? 除非涉及修改外部依赖关系的代码,否则我不关心它是否是一种破解方式。

如果它有用:我正在运行Angular 1.2.0。

6 个答案:

答案 0 :(得分:33)

我一直试图将requirejs + Angular混合一段时间。到目前为止,我在Github(angular-require-lazy)中发布了一个小项目,因为范围对于内联代码或小提琴来说太大了。该项目证明了以下几点:

  • AngularJS模块是延迟加载的。
  • 指令也可以延迟加载。
  • 有一个“模块”发现和元数据机制(参见我的其他宠物项目:require-lazy
  • 应用程序自动拆分为捆绑包(即使用r.js构建工作)

如何完成:

  • 提供商(例如$controllerProvider$compileProvider)是从config函数(我在angularjs-requirejs-lazy-controllers中首次看到的技术)中捕获的。
  • 引导后,angular被我们自己的包装器取代,可以处理延迟加载的模块。
  • 捕获注射器并作为承诺提供。
  • AMD模块可以转换为Angular模块。

这个实现满足了你的需求:它可以延迟加载Angular模块(至少我正在使用的ng-grid),绝对是hackish :)并且不会修改外部库。

非常欢迎评论/意见。


(编辑)此解决方案与其他解决方案的区别在于它不执行动态require()调用,因此可以使用r.js(以及我的require-lazy项目)构建。除此之外,这些想法或多或少地在各种解决方案中趋同。

祝所有人好运!

答案 1 :(得分:7)

注意:使用Nikos Paraskevopoulos的解决方案,因为它更可靠(我正在使用它),并且有更多的例子。


好的,我终于通过对answer的简短帮助找到了如何实现这一目标。

正如我在我的问题中所说,这已成为一种非常黑客的方式。它包含在app模块的上下文中应用依赖模块的_invokeQueue数组中的每个函数。

这是这样的(请在moduleExtender函数中加注):

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
    };
});

完成此操作后,我只需要执行此操作:

app.extend( "ui.codemirror" ); // ui.codemirror module will now be available in my application
app.controller( "FirstController", [ ..., function() { });

答案 2 :(得分:4)

关键是你的app模块所依赖的任何模块也需要是一个延迟加载模块。这是因为提供者和实例缓存对其$ injector服务的角度使用是私有的,并且在初始化完成后它们不公开注册新模块的方法。

所以'hacky'这样做的方法是编辑你想要延迟加载的每个模块,以便需要一个延迟加载模块对象(在你链接的示例中,模块位于文件'appModules.js中'),然后编辑每个控制器,指令,工厂等调用,改为使用app.lazy.{same call}

之后,您可以通过查看应用程序路由如何延迟加载来继续关注您链接到的示例项目('appRoutes.js'文件显示了如何执行此操作)。

不太确定这是否有帮助,但祝你好运。

答案 3 :(得分:2)

有一个指令可以做到这一点:

https://github.com/AndyGrom/loadOnDemand

示例:

<div load-on-demand="'module_name'"></div>

答案 4 :(得分:0)

现有的延迟加载技术存在的问题是他们做了我想做的事情。

例如,使用requirejs,我想打电话:

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

然而,它不起作用。为什么?据我所知,AngularJS只是将模块标记为“将来要加载”,例如,如果我稍等一下,它会起作用 - 我将能够使用它。所以在上面的函数中,我想调用一些函数,如loadPendingModules();

在我的项目中,我创建了一个简单的提供程序('lazyLoad')来完成这个事情,仅此而已,所以现在,如果我需要完全加载一些模块,我可以执行以下操作:

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
        }]);
    };

    // ........

});

这里是源文件的链接(MPL许可证): https://github.com/lessmarkup/less-markup/blob/master/LessMarkup/UserInterface/Scripts/Providers/lazyload.js

答案 5 :(得分:0)

我发给你样例代码。它对我来说很好。所以请检查一下:

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
        });
}])

在HTML页面中:

<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>