如何将require.js添加到我的AngularJS应用程序中?

时间:2014-05-27 13:23:17

标签: angularjs requirejs

我的AngularJS应用程序中有控制器,目前编码如下:

app.controller('appController',
    [
        '$state',
        '$timeout',
        'enumService',
        'userService',
        'utilityService',
        appController
    ]);

function appController(
    $scope,
    $state,
    $timeout,
    enumService,
    userService,
    utilityService
) {

    ...

}

我想要开始做的是使用require.js来处理控制器的延迟加载。我了解到我应该使用这样的东西:

require(["app"], function (app) {
     app.controller('appController', function appController(
         $scope,
         $state,
         $timeout,
         enumService,
         userService,
         utilityService
     ) {

         ...

     });
});

有人可以向我解释app.controller如何获得对服务的引用?我还需要在require.js端做任何其他事情吗?我是否按照我编写appController的方式走上正轨?

1 个答案:

答案 0 :(得分:28)

<强> TL;博士;最终解决方案在最后一部分,或者只看this plunk


使用$injector

进行延迟加载

angular-requirejs-seed项目说明了如何通过设置这样的惰性函数轻松实现延迟加载:

define([], function() {
    return ['$scope', '$http', 'myInjectable', function($scope, $http, myInjectable) {
        $scope.welcomeMessage = 'hey this is myctrl2.js!';

        // because this has happened async, we've missed the digest cycle
        $scope.$apply();
    }];
});

...然后像这样实例化控制器:

.controller('MyCtrl2', ['$scope', '$injector', function($scope, $injector) {
    require(['controllers/myctrl2'], function(myctrl2) {
        $injector.invoke(myctrl2, this, {'$scope': $scope});
    });
...

请注意,延迟加载的函数不是控制器。它只是一个使用$injector调用的函数,可以访问实际控制器的$scopethis,并允许它访问任何

您可以将同样的技术应用于服务工厂指令


延迟加载警告

在大多数情况下,延迟加载可能是弄巧成拙的。如果你的目标是给你的用户一个活泼的网站,那么懒洋洋地加载每个控制器是一个坏主意。建立HTTP连接后,大多数互联网连接允许大量数据在短时间内通过线路流动。然而,延迟可能是真正的杀手。这就是为什么现在大多数网站使用连接和缩小来打包他们的javascript并减少网络请求的数量,而不是依赖延迟加载来增加请求的数量。

考虑您的应用的架构。您将创建多少个可重复使用的指令?应用程序的各个部分之间将共享多少代码,不适合延迟加载?对于许多应用程序,大部分代码都将由常见组件组成,使得延迟加载毫无意义

延迟加载在具有非常独特且独立的部分的应用程序中是有意义的。如此独特且独立的碎片可被视为单独的应用程序。但是,即使在这种情况下,您也可以考虑实际创建单独的应用程序而不是组合它们。

  
     ,即使你不是懒惰加载,require.js仍然有用      

即使您没有延迟加载, require.js也非常有用   依赖管理。与require.js一起使用   优化器它是一种跟踪依赖关系的优雅方式   压缩+缩小你的应用程序。

     

您还可以使用require.js加载运行Jasmine的依赖项   单元测试有助于保持组件模块化,并加快速度   只需加载所需的依赖项即可进行测试。对于单位   测试时,我创建了一个单独的 main-test.js 文件来调用   require.config(...)加载应用依赖项以及   特定于测试的依赖项。

     

延迟加载架构

带角度的延迟加载相当复杂,因为角度不是为了支持延迟加载而设计的。在本节中,我将尝试带您探索如何强制角度来支持延迟加载。这不是一个成熟的解决方案,但我希望提出在构建此类应用程序时理解的重要概念。

让我们从路由器开始,而不是我在第一部分中提到的 angular-requirejs-seed ,它实际上对延迟加载更有意义。您的应用程序的路由器。使用 ui-router ,我们可以通过这种方式实现延迟加载:

...
app.$controllerProvider = $controllerProvider;
var lazyPartialDeferred;

$stateProvider
  ...
  .state('lazy', {
    url: "/lazy",
    templateProvider: function() { return lazyPartialDeferred.promise; },
    controller: 'lazyCtrl',
    resolve: {
      load: function($q, $templateCache) {
        var lazyCtrlDeferred = $q.defer();
        lazyPartialDeferred = $q.defer();
        require(['lazy'], function (lazy) {
          lazyCtrlDeferred.resolve();
          lazyPartialDeferred.resolve($templateCache.get('lazy.html'));
        });
        return lazyCtrlDeferred.promise;
      }
    }
  });
...

我们在这里做的是推迟实例化部分(lazy.html)和控制器(lazyCtrl),直到我们的requirejs模块(lazy.js)被加载。另请注意,我们直接从$ templateCache 加载视图partial, lazy.html 。也就是说,当我们加载lazy.js时,部分本身包含在lazy.js中。从理论上讲,我们可以将lazy.html与lazy.js分开加载,但为了获得最佳性能,我们应该将partials编译成我们的js文件。

让我们来看看 lazy.js

define(['angular', 'lazy-partials'], function (angular) {
  var app = angular.module('app');

  var lazyCtrl =  ['$scope', '$compile', '$templateCache', function ($scope, $compile, $templateCache) {
    $scope.data = 'my data';
  }];

  app.$controllerProvider.register('lazyCtrl', lazyCtrl);
});

请注意,上面的代码代表未编译的 lazy.js.在生产中,lazy-partials.js(在上面的第一行中引用)实际上将被编译到同一个文件中。

现在让我们来看看 lazy-partials.js

// Imagine that this file was actually compiled with something like grunt-html2js
// So, what you actually started with was a bunch of .html files which were compiled into this one .js file...
define(['angular'], function (angular) {
  var $injector = angular.element(document).injector(),
      $templateCache = $injector.get('$templateCache');

  $templateCache.put('lazy.html', '<p>This is lazy content! and <strong>{{data}}</strong> <a href="#">go back</a></p>');
});

再一次,上面的代码并不完全是这样一个文件的样子。 lazy-partials.js 实际上是使用像grunt-html2js这样的构建工具插件从你的html文件自动生成的。

现在,您可以使用迄今为止提供的方法构建整个应用程序。但是,它有点......笨拙。我们宁愿在lazy.js中做的是实例化一个新模块,比如appLazy = angular.module('app.lazy'),然后实例化我们的控制器,指令,服务等,如appLazy.directive(...)

然而,我们无法做到这一点的原因是因为所有这些内容都已在angular.bootstrap方法中初始化(并可供我们的应用使用),该方法已经被时间调用lazy.js 已加载。我们无法再次致电angular.bootstrap(...)

  
     ,内部角度正在执行此操作以引导模块:
      var injector = createInjector(modules, config.strictDi);
     

createInjector是角度循环的内部函数   所有模块和寄存器都是各种构建块。

     

lazy.js 中,我们将$controllerProvider.register(..)称为   懒洋洋地注册我们的控制器。 createInjector也触发了一个   在应用程序引导时调用相同的函数。这是一个清单   各种角度构建块及其形式   由angular注册:

provider: $provide.provider
factory: $provide.factory
service: $provide.service
value: $provide.value
constant: $provide.constant.unshift
animation: $animateProvider.register
filter: $filterProvider.register
controller: $controllerProvider.register
directive: $compileProvider.directive
     

那么,有没有办法懒惰地实例化模块?是的, you can register a module and it's sub-modules by iterating through various nested properties of the module object (requires and _invokeQueue) ,这个操作在一个名为 ocLazyLoad 的库中被简化了。

本节中提供的大部分代码均可在 this plunker 中找到。

(以上未提及的灵感来源:Couch PotatoAngularAMD


完整的延迟加载解决方案:

[ocLazyLoad + ui-router + requirejs] - plunk

因为 ui-router 允许我们推迟加载模板和控制器,我们可以将它与 ocLazyLoad 结合使用,以便在运行之间加载模块路线变化。这个例子基于上一节的原理,但是通过利用 ocLazyLoad ,我们有一个解决方案,允许我们的延迟加载模块的结构与非延迟加载的模块相同。

这里的关键部分是我们的app.config(..)块:

  app.config(function($stateProvider, $locationProvider, $ocLazyLoadProvider) {
    var lazyDeferred;

    $ocLazyLoadProvider.config({
      loadedModules: ['app'],
      asyncLoader: require
    });

    $stateProvider
      ...
      .state('lazy', {
        url: "/lazy",
        templateProvider: function() { return lazyDeferred.promise; },
        controller: 'lazyCtrl',
        resolve: {
          load: function($templateCache, $ocLazyLoad, $q) {
            lazyDeferred = $q.defer();
            return $ocLazyLoad.load({
              name: 'app.lazy', 
              files: ['lazy']
            }).then(function() {
              lazyDeferred.resolve($templateCache.get('lazy.html'));
            });
          }
        }
      });
    ...

lazy.js 现在看起来像这样:

define(['angular', 'lazy-partials'], function (angular) {
  var appLazy = angular.module('app.lazy', ['app.lazy.partials']);

  appLazy.controller('lazyCtrl', function ($scope, $compile, $templateCache) {
    $scope.data = 'my data';
  });
});

请注意,在延迟加载方面,此文件不再有任何特殊之处。您可以以非懒惰的方式轻松加载此文件,但它不会知道其中的差异。同样的原则适用于 lazy-partials.js

// Imagine that this file was actually compiled with something like grunt-html2js
// So, what you actually started with was a bunch of .html files which were compiled into this one .js file...
define(['angular'], function (angular) {
  angular.module('app.lazy.partials', [])
    .run(function($templateCache) {
      $templateCache.put('lazy.html', '<p>This is lazy content! and <strong>{{data}}</strong> <a href="#">go back</a></p>');
    });
});

&GT;&GT;&GT; check out the fully-functioning plunk &lt;&lt;&lt;


部署

在部署方面,这个难题的最后一部分是使用requirejs优化器来连接和最小化我们的js文件。理想情况下,我们希望优化器跳过已包含在主应用程序中的依赖项的连接(即:公共文件)。为实现此目标,see this repo及其随附的build.js file


更优雅的解决方案

我们可以通过为 a very elegant solution 添加ui-router装饰器来改进之前的插件。