【问题标题】:How to Lazy load non-angular JavaScript files using AngularJS?如何使用 AngularJS 延迟加载非角度 JavaScript 文件?
【发布时间】:2014-01-01 22:38:56
【问题描述】:

是否可以从 Angular 控制器加载普通的旧 JS 或 AMD 模块?我以前为此使用过 RequireJS。

我之前在一个相当大的项目中使用过 AngularJS 和 RequireJS。我正在开发一个基于 MEAN Stack 种子的新项目,这不使用 requireJS。

我不是很清楚,但是 Angular 有一个加载模块的系统——我可以从我的 Angular 控制器中加载一段特定的 javascript 吗?

有没有办法修改我的 module() 声明以包含额外的常规 javascript 文件?

谢谢!

编辑:为了让大家了解我在做什么,我有一个页面可以编辑几个不同的表单。这些中的每一个都作为“表单”保存到我的数据库中。根据表单的类型,不同的字典值映射到不同子视图中的不同字段。我的一些表格有例如下拉列表或输入列表。这些是不同的,但有关“表单”的其他所有内容都以通用方式处理。

所以我有这个单数的表单控制器来处理一堆不同的表单,我对结果很满意。主要问题在于每个表单都有一组单独的数据,除非我需要,否则我希望避免加载。

我可以通过检查驱动我的 ng-include(加载子表单)的下拉列表来检查我正在加载哪个表单。

在短期内,我刚刚加载了所有内容并在我的范围内创建了命名空间以进行区分。

例如 $scope.form1 和 $scope.form2 用于特定表单的数据/规则。我宁愿不要加载我不需要的js。

编辑 2: http://jsfiddle.net/HB7LU/1320/

function MyCtrl($scope) {       
    $scope.doSomething = function()
    {
     //I'm used to wrapping with e.g require('jquery..... here, can I do the same thing with angular?   
        alert(" I need to run a jquery function here...");
        var xml = $(someblock);
    };
}

我已经对我正在谈论的内容提出了质疑。我想根据控制器中的某些路径加载任意 JS,并且仅在需要时加载。

基本上,我有一些较大的命名空间要加载,具体取决于所选的多个选项之一,并且仅加载它们会很昂贵。

【问题讨论】:

  • 澄清一下,您想使用 Angular 延迟加载非 Angular 代码吗?
  • 是的。我已经更新了我的问题以预先说明这一点。我有一组嵌套的视图,其中下拉框使用第二个控制器加载第二个角度视图(通过包含)。所以我遇到了时序问题,将其直接包含在子模板中
  • 您需要使用 Angular 承诺,创建一个脚本标签并向其添加一个 onload 侦听器,该侦听器将解析该承诺,以便您的逻辑可以在脚本准备好后继续。我将整个模块延迟加载到我的角度应用程序(主模块)中。在引导之后加载特定于 Angular 的代码会更深入一些,但逻辑与我上面列出的基本相同,只是您还必须向 Angular 注册加载的代码。如果您给我更多关于如何帮助您的指导,我将通过发布的答案对此进行扩展。
  • 我在jsfiddle.net/HB7LU/1320举了一个愚蠢的例子
  • 我想一个更可能的例子是假设我有一个选项可以为特定国家/地区加载某些内容,并且每个国家/地区都有 80k 的数据要加载特定验证、列表等。我没有我不想加载 80k x 100 个不同的变体,但我想在选择特定国家/地区时加载该国家/地区的数据。

标签: javascript angularjs lazy-loading


【解决方案1】:

好的,我对我的代码进行了注释,以便它可以解释所有内容。如果您还有其他问题,请告诉我。这是您的 cmets 中进一步解释的问题的解决方案。 Live demo here (click).

标记:

<!DOCTYPE html>
<html ng-app="myApp" ng-controller="myCtrl">
<head>
</head>
<body>

  <button ng-click="load()">Load Foo</button>
  <!-- I'm using this to bootstrap the lazy loaded script -->
  <section ng-repeat="item in loaded" lazy="item"></section>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.min.js"></script>  
<script src="script.js"></script>
</body>
</html>

JavaScript:

var app = angular.module('myApp', []);

app.controller('myCtrl', function($scope) {
  //array of things to load
  $scope.lazyThings = [
    {directive:'my-foo-directive', file:'foo.js'}  
  ];
  $scope.loaded = [];
  $scope.load = function() {
    var loadIndex = $scope.loaded.length;
    if ($scope.lazyThings[loadIndex]) {
      $scope.loaded.push($scope.lazyThings[loadIndex]);
    }
  }
});

app.factory('myService', function($http) {
  return {
    getJs: function(path) {

      return $http.get(path).then(function(response) {
        deferred.resolve(response.data);
      });

    }
  }
});

//this adds an attribute to kick off the directive 
//for whatever script was lazy loaded
app.directive('lazy', function($compile, $q, myService) {
  var directiveReturn = {
    restrict: 'A',
    scope: {
      lazy: '='
    },
    link: function(scope, element) {
      myService.getJs(scope.lazy.file).then(function(data) {
        return addScript(scope.lazy.file, data, scope);
      }).then(function() {
        var $span = angular.element('<span></span>').attr(scope.lazy.directive, '');
        $compile($span)(scope);
        element.append($span);
      });
    }
  }

  var scriptPromises = {};
  function addScript(file, js, scope) {
    if (!scriptPromises[file]) { //if this controller hasn't already been loaded
      var deferred = $q.defer();
      //cache promise)
      scriptPromises[file] = deferred.promise;

      //inject js into a script tag
      var script = document.createElement('script');
      script.src = 'data:text/javascript,' + encodeURI(js);
      script.onload = function() {
        //now the script is ready for use, resolve promise to add the script's directive element
        scope.$apply(deferred.resolve());
      };
      document.body.appendChild(script);
      return deferred.promise;
    }
    else { //this script has been loaded before
      return scriptPromises[loadFile]; //return the resolved promise from cache
    }
  }

  return directiveReturn;
});

app.directive('myFooDirective', function() {
  return {
    restrict: 'A',
    link: function(scope, element) {
      //put the logic from your lazy loaded "foo.js" script here
      element.text(foo.someFunction());
    }
  }
});

延迟加载脚本示例:

var foo = {
  someFunction: function() {
    return 'I am data from a lazy loaded js function!';
  }
};

有很多方法可以实现我在这里演示的概念。想想你想如何使用它,并编写一些指令来执行它。 Angular 让大多数事情变得非常简单。

注意:注入脚本标签是可选的——但我更喜欢这样而不是直接执行脚本。使用脚本标签时,您将能够使用开发工具在“脚本”部分的“资源”下跟踪所有加载的文件。

【讨论】:

  • 我建议查看 Labjs 异步如何加载 js 并以所需的顺序执行。可能是一种有用的延迟加载方法
  • @MichaelCalkins 嗯..我不明白你的意思。看起来这对 Angular 来说是违反直觉的。
  • 绝妙的解决方案!!
  • 干得好 - getJS 方法可以简化为:如果 dfd 不执行任何操作,则返回 $http.get(path)。
  • +1 不错的帖子。无需在myService 的getJs 函数中使用$q,因为$http 已经返回了一个承诺。例如。 getJs: function(path) { return $http.get(path).then(function(response) { return response.data; }) ;}
【解决方案2】:

其实我不太了解 angular 的指令,它是模块化的东西,但是根据我的基本知识,我构建了一些预加载函数来加载我的 JS 文件.... loadScript 函数在我的 app.js 中定义,它包含在主 html 页面中。

function loadScript(src,callback){

    var script = document.createElement("script");
    script.type = "text/javascript";
    if(callback)script.onload=callback;
    document.getElementsByTagName("head")[0].appendChild(script);
    script.src = src;
}

在控制器内部使用类似的东西

app.controller('myCtrl', function($scope) {
  //array of things to load are saved in a JavascriptFile
  loadScript("URL_To_JavascriptFile");
});

【讨论】:

  • 这么简单——这应该是最佳答案
  • 是的,简单易行!谢谢!
【解决方案3】:

Angular 的模块系统不像 RequireJS。 Angular 只关心模块定义和组合,而不关心模块加载。

这意味着 Angular 不会发出加载任何 javascript 的 HTTP 请求,您需要自己负责加载,使用 &lt;script&gt; 标签或其他 javascript 加载器,如 requirejs。

此外,Angular 要求在引导 Angular 应用程序之前加载和配置所有模块。这使得支持 Angular 模块的任何类型的延迟加载方案变得困难。

使 Angular 能够处理模块的更多动态加载是未来版本计划的一项功能。

【讨论】:

  • 我认为这不太准确。我用 Angular 懒加载了很多东西,它相当简单。
  • 你能详细说明你加载什么样的东西吗?当然你可以加载模板,你可以执行任意的ajax请求,但是你不能轻易加载额外的模块。在启动应用程序时,必须在创建注入器之前加载和定义所有模块。
  • 我将整个模块依赖项延迟加载到 Angular 中。在stackoverflow.com/questions/20410447/… 和stackoverflow.com/questions/14855083/… 此处查看我的答案,但这些比加载整个模块更基本一些。为此,我将模块解析为每种类型(控制器、过滤器等)并一一注册。
  • 功能很好,我只是说在当前角度实现这一点的方式可能被认为是一种 hack。它通过获取 ARROUND 模块功能而不是使用它来工作。例如,您动态加载的脚本必须在全局(窗口)范围内定义它的控制器。如果一个人真的需要这个功能并且理解所有的含义,那么使用 hack 就可以了。但我认为在一般情况下,这不应该被认为是一种简单的或推荐的做法。
  • @Yablargo 你绝对不想在 Angular 启动后加载 jQuery,因为 Angular 实际上会使用 jQuery(如果它可用),否则会退回到它自己的 jqLit​​e 实现。我听说人们经常将 RequireJS 与 Angular 一起使用,因此您可能想要使用它……就个人而言,我要么完全避免使用 jQuery,要么直接将其包含在内。我只在需要时才延迟加载其他 Angular 功能。除非您确实需要,否则我实际上不建议延迟加载任何内容。就我而言,这是必须的。
猜你喜欢
  • 1970-01-01
  • 2015-07-01
  • 2017-06-20
  • 1970-01-01
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
相关资源
最近更新 更多