AngularJS:延迟加载控制器和内容

Posted

技术标签:

【中文标题】AngularJS:延迟加载控制器和内容【英文标题】:AngularJS: lazy loading controllers and content 【发布时间】:2014-08-06 19:28:35 【问题描述】:

在这个简化的场景中,我有两个文件: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>

懒惰的.htm

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

结果是错误:“Argument 'lazy' is not a function, got undefined”

使用函数代替

懒惰的.htm

function lazy($scope) 
    $scope.lazy = 'Lazy Controller';

<div ng-controller="lazy">
    lazy
</div>

这在 1.3 beta 14 之前有效。在 beta 15 中删除了全局控制器功能:https://github.com/angular/angular.js/issues/8296

那么现在,动态获取lazy.htm 的角度化内容的更好方法是什么?

更新:

在这篇文章 (http://ify.io/lazy-loading-in-angularjs) 中,我找到了另一种可能的解决方案。 $controllerProvider 允许我们在 angular bootstrap 之后注册新的控制器。奇迹般有效。在 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>

懒惰的.htm

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

更新 2:

另外两个可行的选择是:

懒惰的.htm

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

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

但我认为最后两个示例不应在生产中使用。

【问题讨论】:

我还发现 requirejs 很难与其他库一起使用,反之亦然。这就是为什么我创建了一个更易于使用并使用 Angular 进行测试的库。底部有一个演示应用程序:gngeorgiev.github.io/Modulerr.js 您也可以将所有脚本合二为一,而不依赖于 Modulerr.js 这个问题给了我一生中最美好的时刻之一。通过简单地使用$controllerProvider,现在我可以拥有self-contained 的客户端代码。即 html + JS 在一个文件中。 可以使用requireJs和ocLazyload来动态加载文件和注入模块。参考文章codeproject.com/Articles/1039826/… 在此处尝试 ocLazyload 示例应用 freakyjolly.com/how-to-lazy-load-modules-controllers-angularjs 【参考方案1】:

你也可以使用 jquery 来解析 $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';
);

【讨论】:

这并不总是有效 - 有时 getScript 在视图呈现后执行。 /*这里很神奇,jquery getScript 会在渲染视图之前将脚本放在我想要的目录中*/ 您好,感谢观看,我制作了一个新版本,现在试试 它也将在任何现代构建系统上失败。【参考方案2】:

////JConfig文件--------

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
    

//控制器声明

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

]);

【讨论】:

require 只需要 1 个参数。【参考方案3】:

起初我使用了 André Betiolo 的回答。但是,它并不总是有效,因为 ajax 加载是非阻塞的,导致视图有时会在加载脚本之前请求控制器。

作为一种解决方案,我强制该函数在所有脚本成功加载之前不返回。这有点骇人听闻,但确保在完成解析之前加载成功。它还允许加载多个控制器。

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

【讨论】:

我做了一个新版本,现在试试【参考方案4】:

你可以有纯 AngularJS 延迟加载。

创建“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;
        
    
]);

然后,定义你的配置:

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');
            ]
        
    );
. . .

在“js/services/vendor.js”上,创建服务为:

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

在“js/controllers/vendor.js”上,创建控制器为:

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

when 上的“resolve”属性定义了在路由加载之前应该解决哪些承诺。

【讨论】:

我一直在使用你的解决方案,但现在我想在路线更改时取消请求,我使用资源来处理我的请求,请你帮帮我,tnx【参考方案5】:

按照您的要求,最好的方法是使用指令并将控制器和模板绑定在一起,以便在适当的时间绑定。目前,除非您像第二个示例中所示声明一个全局函数,否则它不会在正确的时间在 lazy.htm 中发生绑定。

【讨论】:

【参考方案6】:

理想情况下 - Angular 会强制您将 HTML 和 JS 分开,因为在新版本中这可能会更频繁地强制执行。

您可能必须使用 requireJS http://solutionoptimist.com/2013/09/30/requirejs-angularjs-dependency-injection/

为了诡计你可以试试

ng-controller-controller="'lazy'"

在 HTML 中

ng-controller-controller="myObject.controller"

某处注入

$scope.myObject.controller = $controller('lazy', $scope: $scope)

【讨论】:

【参考方案7】:

试试这个 ARI plugin 用于 Angular JS。它可以帮助您按需延迟加载控制器脚本。

【讨论】:

【参考方案8】:

你也可以使用指令来加载你的控制器!

这里是一个例子:

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

【讨论】:

【参考方案9】:

我正在向您发送示例代码。它对我来说很好。所以请检查:

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>

谢谢你

【讨论】:

以上是关于AngularJS:延迟加载控制器和内容的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Angular js 的选项卡集中启用延迟加载?

在AngularJS中实现一个延迟加载的Directive

延迟 AngularJS 路由更改直到模型加载以防止闪烁

使用 MEAN 堆栈延迟加载 (AngularJS 1.x)

angularJS使用ocLazyLoad实现js延迟加载

使用 LazySizes 延迟加载轮播图像