Angular-UI-Router与RequireJS搭配使用,实现控制器的延迟加载

13

你能帮我理解如何在下面的示例中在视图之前加载控制器吗?看起来视图立即加载,而控制器尚未加载。

//app.js
$stateProvider.state('index', {
    url: "/",
    views: {
        "topMenu": {
            templateUrl: "/Home/TopMenu",
            controller: function($scope, $injector) {
                require(['controllers/top-menu-controller'], function(module) {
                    $injector.invoke(module, this, { '$scope': $scope });
                });
            }
        }
    }
});

//top-menu-controller.js
define(['app'], function (app) {
    app.controller('TopMenuCtrl', ['$scope', function ($scope) {
        $scope.message = "It works";
    }]);
});

//Home/TopMenu
<h3>TopMenu</h3>
<div ng-controller="TopMenuCtrl">
    {{message}}
</div>

你可能想看一下 angular-require-lazy,它没有使用 angular-ui-router,但其中的一些概念可能会有所帮助。 - Nikos Paraskevopoulos
我在 Github 的 issues 上开了一个问题。https://github.com/angular-ui/ui-router/issues/1002 - Matej
2个回答

13
I在这里创建了一个工作的plunker
让我们有这个index.html:
<!DOCTYPE html>
<html>
  <head>
    <title>my lazy</title>    
  </head>

  <body ng-app="app">
    
      <a href="#/home">#/home</a>     // we have three states - 'home' is NOT lazy
      <a href="#/">#/</a>  - index    // 'index' is lazy, with two views
      <a href="#/other">#/other</a>   // 'other' is lazy with unnamed view
    
    <div data-ui-view="topMenu"></div>        
    <div data-ui-view=""></div>
    
    <script src="angular.js"></script>           // standard angular
    <script src="angular-ui-router.js"></script> // and ui-router scritps

    <script src="script.js"></script>            // our application

    <script data-main="main.js"                  // lazy dependencies
        src="require.js"></script>
     
  </body>    
</html>

让我们观察 main.js - RequireJS 的配置:
require.config({

    //baseUrl: "js/scripts",
    baseUrl: "",

    // alias libraries paths
    paths: { 
      
        // here we define path to NAMES
        // to make controllers and their lazy-file-names independent
        
        "TopMenuCtrl": "Controller_TopMenu",
        "ContentCtrl": "Controller_Content",
        "OtherCtrl"  : "Controller_Other",
    },

    deps: ['app']
});

实际上,我们只为我们的ControllerNames和它们的Controller_Scripts.js文件创建别名(路径)。就是这样。此外,我们返回需要应用程序,但在我们的情况下,我们稍后将使用不同的功能 - 注册惰性加载的控制器。 deps: ['app']的意思是什么?首先,我们需要提供文件app.js ('app'表示查找app.js

define([], function() {

  var app = angular.module('app');
  return app; 
})

这个返回值是我们可以在每个异步加载的文件中请求的。
define(['app'], function (app) {
    // here we would have access to the module("app")
});

我们如何实现控制器的懒加载?就像这里已经证明的那样,对于ngRoute

angularAMD v0.2.1

angularAMD是一种实用工具,支持在AngularJS应用程序中使用RequireJS,并支持按需加载第三方模块,例如angular-ui。

我们将向Angular请求引用$controllerProvider - 稍后将使用它来注册控制器。

这是我们script.js的第一部分:

// I. the application
var app = angular.module('app', [
  "ui.router"
]);


// II. cached $controllerProvider
var app_cached_providers = {};

app.config(['$controllerProvider',
  function(controllerProvider) {
    app_cached_providers.$controllerProvider = controllerProvider;
  }
]);

我们可以看到,我们刚刚创建了名为 'app' 的应用程序,并且还创建了 holder app_cached_providers (遵循 angularAMD 风格)。在配置阶段,我们请求 angular 获取 $controllerProvider 并保留对它的引用。
现在让我们继续在 script.js 中:
// III. inline dependency expression
app.config(['$stateProvider', '$urlRouterProvider',
  function($stateProvider, $urlRouterProvider) {

    $urlRouterProvider
      .otherwise("/home");

    $stateProvider
      .state("home", {
        url: "/home",
        template: "<div>this is home - not lazily loaded</div>"
      });

    $stateProvider
      .state("other", {
        url: "/other",
        template: "<div>The message from ctrl: {{message}}</div>",
        controller: "OtherCtrl",
        resolve: {
          loadOtherCtrl: ["$q", function($q) { 
            var deferred = $q.defer();
            require(["OtherCtrl"], function() { deferred.resolve(); });
            return deferred.promise;
          }],
        },
      });

  }
]);

这部分展示了两种状态声明。其中之一是标准的非懒加载状态'home'。它的控制器是隐式的,但可以使用标准方式。
第二个状态名为"other",它指向未命名的视图ui-view=""。在这里,我们首先可以看到懒加载。在resolve内部(参见:)

解决

您可以使用resolve为状态提供自定义内容或数据。resolve是一个可选的依赖项映射,应该注入到控制器中。

如果这些依赖关系中有任何承诺,它们将在控制器实例化之前被解决并转换为值,并且$stateChangeSuccess事件将被触发。

有了这个,我们知道,控制器(根据其名称)将在解析完成后在Angular存储库中搜索:
// this controller name will be searched - only once the resolve is finished
controller: "OtherCtrl",
// let's ask RequireJS
resolve: {
  loadOtherCtrl: ["$q", function($q) { 
    // wee need $q to wait
    var deferred = $q.defer();
    // and make it resolved once require will load the file
    require(["OtherCtrl"], function() { deferred.resolve(); });
    return deferred.promise;
  }],
},

好的,现在正如上面提到的那样,主要内容包含了这个别名def

// alias libraries paths
paths: {       
    ...
    "OtherCtrl"  : "Controller_Other",

这意味着将搜索并加载文件“Controller_Other.js”。以下是它的内容,它实现了魔法。最重要的是先前缓存的$controllerProvider引用的使用。
// content of the "Controller_Other.js"

define(['app'], function (app) {
    // the Default Controller
    // is added into the 'app' module
    // lazily, and only once
    app_cached_providers
      .$controllerProvider
      .register('OtherCtrl', function ($scope) {
        $scope.message = "OtherCtrl";
    });        
});

"关键是不要使用app.controller(),而是"

$controllerProvider.Register

$controller服务用于Angular创建新的控制器。该提供程序允许通过register()方法进行控制器注册。

最后还有另一个状态定义,具有更加精细的解析... 试图使其更易读:

// IV ... build the object with helper functions
//        then assign to state provider    
var loadController = function(controllerName) {
  return ["$q", function($q) {
      var deferred = $q.defer();
      require([controllerName], function() {deferred.resolve(); });
      return deferred.promise;
  }];
}    

app.config(['$stateProvider', '$urlRouterProvider',
  function($stateProvider, $urlRouterProvider) {

    var index = {
        url: "/",
        views: {
          "topMenu": {
            template: "<div>The message from ctrl: {{message}}</div>",
            controller: "TopMenuCtrl",
          },
          "": {
            template: "<div>The message from ctrl: {{message}}</div>",
            controller: "ContentCtrl",
          },
        },
        resolve : { },
    };        
    index.resolve.loadTopMenuCtrl = loadController("TopMenuCtrl");
    index.resolve.loadContentCtrl = loadController("ContentCtrl");
    
    $stateProvider
      .state("index", index);          
}]);

以上我们可以看到,我们解决了该状态下两个/所有命名视图的两个控制器。
就是这样。在这里定义了每个控制器。
paths: { 
    "TopMenuCtrl": "Controller_TopMenu",
    "ContentCtrl": "Controller_Content",
    "OtherCtrl"  : "Controller_Other",
    ...
},

将通过resolve异步加载,$controllerProvider将通过RequireJS进行懒加载。请检查所有这里

类似的问题和回答:AngularAMD + ui-router + 动态控制器名称?


0
在一个项目中,我使用了控制器的惰性加载,并且必须手动调用 $digest 才能使它正常工作。我猜想这种行为在 ui-router 中也不会改变。 你试过了吗?
define(['app'], function (app) {
  app.controller('TopMenuCtrl', ['$scope', function ($scope) {
    $scope.message = "It works";
    $scope.$digest();
  }]);
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接