如何使用 angular-ui-router 在一个请求中延迟加载控制器和模板

How to Lazyload controller and template in one request using angular-ui-router

我正在尝试延迟加载组件。该组件是一个 html 片段,带有包含控制器的嵌入式脚本标签。

<script>
    ... controller code .....
</script>
<div>
    ... template ....
</div>

该片段是在一个 html 请求中生成的,所以我不能在状态定义中使用 templateUrl 和 componentURL。

我尝试使用 templateProvider 获取组件,而不是提取函数的脚本代码并使用对 controllerProvider 的引用进行注册。

我相信一定有比我想出的丑陋解决方案更好的方法来做到这一点。我对 controllerpovider 进行全局引用,然后使用 getComponent 服务通过 templateProvide 读取组件。接下来我提取脚本并对其进行评估,这也注册了控制器。

请参阅 plunker 了解我尝试解决此问题的方法。

.factory('getComponent', function($http, $q) {
  return function (params) {
    var d = $q.defer();
    // optional parameters
    $http.get('myComponent.html').then(function (html) {
            // the component contains a script tag and the rest of the template.
            // the script tags contain the controller code.
            // first i extract the two parts
            var parser = new window.DOMParser();
            var doc = parser.parseFromString(html.data, 'text/html');
            var script = doc.querySelector('script');
            // Here is my problem. I now need to instantiate and register the controller. 
            // It is now done using an eval which of cours is not the way to go
            eval(script.textContent);
            // return the htm which contains the template
            var html = doc.querySelector('body').innerHTML;
            d.resolve(html);
    });
    return d.promise;
  };
})

也许可以使用 templateProvider 和 controllerProvider 来完成,但我不确定如何通过一个 http 请求解决这两个问题。任何帮助/想法将不胜感激。

Here's a working plunkr

  • 您在运行时无权访问 $controllerProvider,因此您无法注册命名控制器。
    • UI-路由器不需要named/registered控制器功能。 { controller: function() {} } 在状态定义中完全有效
    • 但是,如果您需要注册控制器,您可以使用 ocLazyLoad 之类的工具来注册它。
  • UI-路由器将控制器链接到模板,因此不需要 ng-controller散布在 html。

下面是我如何破解这个的。 getController 工厂现在保留 "promises for components" 的缓存。我保留了您的评估和模板解析以将响应分成两部分。我用一个包含 ctrl/template.

的对象解决了这个承诺

组件工厂

  .factory('getComponent', function($http, $q) {
      var components = {};
      return function (name, params) {
        if (components[name]) 
          return components[name];

        return components[name] = $http.get(name + '.html').then(extractComponent);

        function extractComponent(html) {
          var parser = new window.DOMParser();
          var doc = parser.parseFromString(html.data, 'text/html');
          var script = doc.querySelector('script');
          // returns a function from the <script> tag
          var ctrl = eval(script.textContent);
          // return the htm which contains the template
          var tpl = doc.querySelector('body').innerHTML;
          // resolve the promise with this "component"
          return {ctrl: ctrl, tpl: tpl};
        }
      };
  })

myComponent.html

<script>
  var controller = function($scope) {
    $scope.sayHi = function() {
      alert(' Hi from My controller')
    }
  };
  // This statement is assigned to a variable after the eval()
  controller;
</script>

// No ng-controller
<div>
  Lazyloaded template with controller in one pass.
  <button ng-click="sayHi()">Click me to see that the controller actually works.</button>
</div>

状态定义中:

  • 我创建了一个名为 'component' 的解决方案。该解析要求 getComponent 工厂获取名为 'myComponent' 的组件(提示:'myComponent' 可能是路由参数)
  • 准备就绪后,解析将公开给 UI-路由器状态子树。
  • 状态已激活
  • 视图已初始化
    • 控制器提供者和模板提供者将解析的 component 和 return controller/template 注入到 UI-Router。

气味测试

我应该提到,在单个 html 文件中获取控制器和模板并手动解析对我来说是错误的。

您可以追求的一些变化:

  • 改进getComponent工厂;将组件拆分为 html/js 并分别获取每个组件。使用 $q.all 等待两次提取。
  • 使用 ocLazyLoad 向 $controllerProvider 全局注册控制器
  • 获取模板并将其存储在 $templateCache 中。