草庐IT

javascript - 更改状态时延迟加载 Angular 组件脚本

coder 2024-07-23 原文

这个问题在过去一天左右一直存在。

我一直在尝试让我的 AngularJS 应用程序延迟加载每个状态组件的脚本文件。我正在使用 Angular 进行一个大型项目,并且 index.html文件已变成超过 100 个 <script>标签包括各种 Controller 、服务和库的 JS。它们中的大多数都很小,所以加载时间并不是一个大问题(尽管它可能是),但它对我来说从来都不是干净的。

也许是因为我已经习惯了 PHP 的自动加载器,或者刚刚被所有可以在编译时加载自己的依赖项的语言宠坏了。必须在应用程序的根文档中为一些次要的、边缘状态的指令加载脚本,或者如果指令实际属于的模块在没有 <script> 的情况下移动到另一个应用程序中时不加载脚本本身,这不是模块化的。荣耀榜单。

无论哪种方式,我都在开始一个新项目并希望让它更干净,但是以这种方式将组件加载到 Angular 中会带来许多挑战。很多问题已经在文档或一些博客文章、SO 问题或其他问题中一次又一次地得到解决,但我还没有看到一个端到端的解决方案可以与其他 Angular 组件干净地集成。

  1. Angular 仅引导 ng-app如果在呈现页面时 Angular 和模块已经加载,则使用指令。即使以延迟加载启动应用程序也需要解决方法。
  2. 模块 API 的方法仅在应用程序启动之前起作用。在启动应用程序之后注册新的 Controller 、指令、过滤器或服务,但在实际加载定义它们的脚本之后(以及在实际需要它们时)需要一种解决方法。
  3. 延迟加载脚本和调用基于 AJAX 的服务都需要调用回调,将服务调用的结果注入(inject)状态 Controller 需要这些服务实际存在,以便在状态转换开始时调用。实际上调用一个延迟加载的服务并在状态改变之前解决它......需要一个解决方法。
  4. 所有这些都需要以一种看起来不笨拙的方式组合在一起,并且可以轻松地在多个应用程序中重复使用,而无需每次都重新发明轮子。

我看过 #1 和 #2 的答案。显然, angular.bootstrap 可用于在没有 ng-app 的情况下加载整个页面后启动模块指示。自举后添加组件不太明显,但是 saving references to the various $provider services in the config blocks does the trick , overwriting the module API more seamlessly so .解决 #3 并以满足 #4 的方式完成这一切有点难以捉摸。

上面解决 #2 的例子是针对 Controller 和指令的。添加服务变得有点复杂,异步服务,延迟加载,并且意味着将它们的数据提供给延迟加载的 Controller ,尤其如此。关于 Isitor 先生,他的代码当然适用于注册 Controller 作为概念证明,但代码的编写方式无法轻松扩展到延迟加载脚本有意义的应用程序类型,a具有数十到数百个包含、依赖项和异步服务的更大的应用程序。

我将发布我想出的解决方案,但如果有人有改进它的建议,或者已经找到了截然不同的更好方法,请随时添加。

最佳答案

这是 Angular 模块 lazy 的代码,它依赖于 ui.router 模块。当它包含在模块的依赖项中时,状态脚本的延迟加载功能将被启用。我包含了主要应用程序模块的示例、一些惰性组件和我的 index.html,为演示目的进行了清理。我正在使用 Script.js实际处理脚本加载的库。

angular-ui-router-lazy.js

/**
 * Defines an AngularJS module 'lazy' which depends on and extends the ui-router
 * module to lazy-load scripts specified in the 'scripts' attribute of a state
 * definition object.  This is accomplished by registering a $stateChangeStart
 * event listener with the $rootScope, interrupting the associated state change
 * to invoke the included $scriptService which returns a promise that restarts the
 * previous state transition upon resolution.  The promise resolves when the
 * extended Script.js script loader finishes loading and inserting a new <script>
 * tag into the DOM.
 *
 * Modules using 'lazy' to lazy-load controllers and services should call lazy.makeLazy
 * on themselves to update the module API to inject references for the various $providers 
 * as the original methods are only useful before bootstrapping, during configuration,
 * when references to the $providers are in scope.  lazy.makeLazy will overwrite the
 * module.config functions to save these references so they are available at runtime,
 * after module bootstrapping.
 * See http://ify.io/lazy-loading-in-angularjs/ for additional details on this concept
 *
 * Calls to $stateProvider.state should include a 'scripts' property in the object
 * parameter containing an object with properties 'controllers', 'directives', 'services',
 * 'factories', and 'js', each containing an array of URLs to JS files defining these
 * component types, with other miscelleneous scripts described in the 'js' array.
 * These scripts will all be loaded in parallel and executed in an undefined order
 * when a state transition to the specified state is started.  All scripts will have
 * been loaded and executed before the 'resolve' property's promises are deferred,
 * meaning services described in 'scripts' can be injected into functions in 'resolve'.
 */

 (function() {
    // Instantiate the module, include the ui.router module for state functionality
    var lazy = angular.module('lazy',['ui.router']);

    /**
     * Hacking Angular to save references to $providers during module configuration.
     * 
     * The $providers are necessary to register components, but they use a private injector
     * only available during bootstrap when running config blocks.  The methods attached to the
     * Vanilla AngularJS modules rely on the same config queue, they don't actually run after the
     * module is bootstrapped or save any references to the providers in this injector.
     * In makeLazy, these methods are overwritten with methods referencing the dependencies
     * injected at configuration through their run context.  This allows them to access the
     * $providers and run the appropriate methods on demand even after the module has been
     * bootstrapped and the $providers injector and its references are no longer available.
     *
     * @param module      An AngularJS module resulting from an angular.module call.
     * @returns module    The same module with the provider convenience methods updated
     * to include the DI $provider references in their run context and to execute the $provider
     * call immediately rather than adding calls to a queue that will never again be invoked.
     */
    lazy.makeLazy = function(module) {
      // The providers can be injected into 'config' function blocks, so define a new one
      module.config(function($compileProvider,$filterProvider,$controllerProvider,$provide) {
        /**
         * Factory method for generating functions to call the appropriate $provider's
         * registration function, registering a provider under a given name.
         * 
         * @param registrationMethod    $provider registration method to call
         * @returns function            A function(name,constructor) calling
         * registationMethod(name,constructor) with those parameters and returning the module.
         */
        var register = function(registrationMethod) {
          /**
           * Function calls registrationMethod against its parameters and returns the module.
           * Analogous to the original module.config methods but with the DI references already saved.
           *
           * @param name          Name of the provider to register
           * @param constructor   Constructor for the provider
           * @returns module      The AngularJS module owning the providers
           */
          return function(name,constructor) {
            // Register the provider
            registrationMethod(name,constructor);
            // Return the module
            return module;
          };
        };

        // Overwrite the old methods with DI referencing methods from the factory
        // @TODO: Should probably derive a LazyModule from a module prototype and return
        // that for the sake of not overwriting native AngularJS code, but the old methods
        // don't work after `bootstrap` so they're not necessary anymore anyway.
        module.directive = register($compileProvider.directive);
        module.filter = register($filterProvider.register);
        module.controller = register($controllerProvider.register);
        module.provider = register($provide.provider);
        module.service = register($provide.service);
        module.factory = register($provide.factory);
        module.value = register($provide.value);
        module.constant = register($provide.constant);
      });
      // Return the module
      return module;
    };

    /**
     * Define the lazy module's star $scriptService with methods for invoking
     * the extended Script.js script loader to load scripts by URL and return
     * promises to do so.  Promises require the $q service to be injected, and
     * promise resolutions will take place in the Script.js rather than Angular
     * scope, so $rootScope must be injected to $apply the promise resolution
     * to Angular's $digest cycles.
     */
    lazy.service('$scriptService',function($q,$rootScope) {
      /**
       * Loads a batch of scripts and returns a promise which will be resolved
       * when Script.js has finished loading them.
       *
       * @param url   A string URL to a single script or an array of string URLs
       * @returns promise   A promise which will be resolved by Script.js
       */
      this.load = function(url) {
        // Instantiate the promise
        var deferred = $q.defer();
        // Resolve and bail immediately if url === null
        if (url === null) { deferred.resolve(); return deferred.promise; }
        // Load the scripts
        $script(url,function() {
          // Resolve the promise on callback
          $rootScope.$apply(function() { deferred.resolve(); });
        });
        // Promise that the URLs will be loaded
        return deferred.promise;
      };

      /**
       * Convenience method for loading the scripts specified by a 'lazy'
       * ui-router state's 'scripts' property object.  Promises that all
       * scripts will be loaded.
       *
       * @param scripts   Object containing properties 'controllers', 'directives',
       * 'services', 'factories', and 'js', each containing an array of URLs to JS
       * files defining those components, with miscelleneous scripts in the 'js' array.
       * any of these properties can be left off of the object safely, but scripts
       * specified in any other object property will not be loaded.
       * @returns promise   A promise that all scripts will be loaded
       */
      this.loadState = function(scripts) {
        // If no scripts are given, instantiate, resolve, and return an easy promise
        if (scripts === null) { var d = $q.defer; d.resolve(); return d; }
        // Promise that all these promises will resolve
        return $q.all([
          this.load(scripts['directives'] || null),
          this.load(scripts['controllers'] || null),
          this.load(scripts['services'] || null),
          this.load(scripts['factories'] || null),

          this.load(scripts['js'] || null)
        ]);
      };
    });

    // Declare a run block for the module accessing $rootScope, $scriptService, and $state
    lazy.run(function($rootScope,$scriptService,$state) {
      // Register a $stateChangeStart event listener on $rootScope, get a script loader
      // for the $rootScope, $scriptService, and $state service.
      $rootScope.$on('$stateChangeStart',scriptLoaderFactory($scriptService,$state));
    });

    /**
     * Returns a two-state function for handing $stateChangeStart events.
     * In the first state, the handler will interrupt the event, preventing
     * the state transition, and invoke $scriptService.loadState on the object
     * stored in the state definition's 'script' property.  Upon the resolution
     * of the loadState call, the handler restarts a $stateChangeStart event
     * by invoking the same transition.  When the handler is called to handle
     * this second event for the original state transition, the handler is in its
     * second state which allows the event to continue and the state transition
     * to happen using the ui-router module's default functionality.
     *
     * @param $scriptService    Injected $scriptService instance for lazy-loading.
     * @param $state            Injected $state service instance for state transitions.
     */
    var scriptLoaderFactory = function($scriptService,$state) {
      // Initialize handler state
      var pending = false;
      // Return the defined handler
      return function(event,toState,toParams,fromState,fromParams) {
        // Check handler state, and change state
        if (pending = !pending) {   // If pending === false state
          // Interrupt state transition
          event.preventDefault();
          // Invoke $scriptService to load state's scripts
          $scriptService.loadState(toState.scripts)
            // When scripts are loaded, restart the same state transition
            .then(function() { $state.go(toState,toParams); });
        } else {  // If pending === true state
          // NOOP, 'ui-router' default event handlers take over
        }
      };
    };
  })();

/** End 'lazy' module */

index.html

<!DOCTYPE html>
<html>
  <head>
    <title>Lazy App</title>
    <script type='text/javascript' src='libs/script.js'></script>
    <script type='text/javascript'>
      $script.queue(null,'libs/angular/angular.min.js','angular')
             .queue('angular','libs/angular/angular-ui-router.min.js','ui-router')
             .queue('ui-router','libs/angular/angular-ui-router-lazy.js','lazy')
             .queue('lazy',null,'libs-angular')

             .queue('libs-angular','lazyapp/lazyapp.module.js','lazyapp-module');

      $script.ready('lazyapp-module',function() { console.log('All Scripts Loaded.'); });
    </script>
  </head>

  <body>
    <div ui-view='mainView'></div>
  </body>
</html>

功能被黑入 Script.js因为我更喜欢语法

$script.queue = function(aQueueBehind,aUrl,aLabel) {
  if (aQueueBehind === null) { return $script((aUrl === null?[null]:aUrl),aLabel); }
  $script.ready(aQueueBehind,function() {
    if (aUrl !== null)
      $script(aUrl,aLabel);
    else
      $script.done(aLabel);
  });
  return $script;
}

lazyapp.module.js

(function() {
  var lazyApp = angular && angular.module('lazyApp ',['lazy']);
  lazyApp = angular.module('lazy').makeLazy(lazyApp);

  lazyApp.config(function($stateProvider) {

    $stateProvider.state({
      name: 'root',
      url: '',
      views: {
        'mainView': { templateUrl: '/lazyapp/views/mainview.html', controller: 'lazyAppController' }
      },
      scripts: {
        'directives': [ 'lazyapp/directives/lazyheader/src/lazyheader.js' ],
        'controllers': [ 'lazyapp/controllers/lazyappcontroller.js' ],
        'services': [ 'lazyapp/services/sectionservice.js' ]
      },
      resolve: {
        sections: function(sectionService) {
          return sectionService.getSections();
        }
      }
    });
  });

  angular.bootstrap(document,['lazyApp']);
})();

sectionservice.js

(function() {
  var lazyApp = angular.module('lazyApp');

  lazyApp.service('sectionService',function($q) {
    this.getSections = function() {
      var deferred = $q.defer();
      deferred.resolve({
        'home': {},
        'news': {},
        'events': {},
        'involved': {},
        'contacts': {},
        'links': {}
      });
      return deferred.promise;
    };
  });
})();

lazyheader.js

(function() {
  var lazyApp = angular.module('lazyApp ');

  lazyApp.directive('lazyHeader',function() {
    return {
      templateUrl: 'lazyapp/directives/lazyheader/templates/lazyheader-main.html',
      restrict: 'E'
    };
  });
})();

lazyappcontroller.js

(function() {
  var lazyApp = angular.module('lazyApp ');

  lazyApp.controller('lazyAppController',function(sections) {
    // @TODO: Control things.
    console.log(sections);
  });
})();

关于javascript - 更改状态时延迟加载 Angular 组件脚本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21742495/

有关javascript - 更改状态时延迟加载 Angular 组件脚本的更多相关文章

  1. ruby-on-rails - Ruby on Rails 迁移,将表更改为 MyISAM - 2

    如何正确创建Rails迁移,以便将表更改为MySQL中的MyISAM?目前是InnoDB。运行原始执行语句会更改表,但它不会更新db/schema.rb,因此当在测试环境中重新创建表时,它会返回到InnoDB并且我的全文搜索失败。我如何着手更改/添加迁移,以便将现有表修改为MyISAM并更新schema.rb,以便我的数据库和相应的测试数据库得到相应更新? 最佳答案 我没有找到执行此操作的好方法。您可以像有人建议的那样更改您的schema.rb,然后运行:rakedb:schema:load,但是,这将覆盖您的数据。我的做法是(假设

  2. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  3. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  4. ruby - 如何在续集中重新加载表模式? - 2

    鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende

  5. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  6. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  7. ruby - Capistrano 3 在任务中更改 ssh_options - 2

    我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

  8. ruby-on-rails - 跳过状态机方法的所有验证 - 2

    当我的预订模型通过rake任务在状态机上转换时,我试图找出如何跳过对ActiveRecord对象的特定实例的验证。我想在reservation.close时跳过所有验证!叫做。希望调用reservation.close!(:validate=>false)之类的东西。仅供引用,我们正在使用https://github.com/pluginaweek/state_machine用于状态机。这是我的预订模型的示例。classReservation["requested","negotiating","approved"])}state_machine:initial=>'requested

  9. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

    我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

  10. ruby - 更改 ActiveRecord 中对象的类 - 2

    假设我有一个FireNinja我的数据库中的对象,使用单表继承存储。后来才知道他真的是WaterNinja.将他更改为不同的子类的最干净的方法是什么?更好的是,我很想创建一个新的WaterNinja对象并替换旧的FireNinja在数据库中,保留ID。编辑我知道如何创建新的WaterNinja来self现有FireNinja的对象,我也知道我可以删除旧的并保存新的。我想做的是改变现有项目的类别。我是通过创建一个新对象并执行一些ActiveRecord魔法来替换行,还是通过对对象本身做一些疯狂的事情,或者甚至通过删除它并使用相同的ID重新插入来做到这一点,这是问题的一部分。

随机推荐