【问题标题】:AngularJS: lazy loading controllers and contentAngularJS:延迟加载控制器和内容
【发布时间】: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

标签: javascript jquery angularjs lazy-loading controllers


【解决方案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>
          

          谢谢你

          【讨论】:

            猜你喜欢
            • 2016-10-10
            • 1970-01-01
            • 2014-10-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-08-12
            相关资源
            最近更新 更多