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

Sam*_*tar 12 requirejs angularjs

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

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

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

    ...

}
Run Code Online (Sandbox Code Playgroud)

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

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

         ...

     });
});
Run Code Online (Sandbox Code Playgroud)

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

Gil*_*man 28

TL;博士; 最后的解决方案是在最后一节或只是看看这个插件


延迟加载 $injector

角requirejs种子项目说明了如何您可以轻松地通过设置一个懒惰的功能这样实现延迟加载:

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();
    }];
});
Run Code Online (Sandbox Code Playgroud)

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

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

请注意,延迟加载的函数不是控制器.这只是与调用的函数$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;
      }
    }
  });
...
Run Code Online (Sandbox Code Playgroud)

我们在这里做的是推迟部分(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);
});
Run Code Online (Sandbox Code Playgroud)

请记住,上面的代码代表未编译的 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>');
});
Run Code Online (Sandbox Code Playgroud)

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

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

但是,我们无法做到这一点的原因是因为所有这些东西都在lazy.js加载angular.bootstrap时调用的方法中被初始化(并且可供我们的应用程序使用).我们不能再打电话了.angular.bootstrap(...)


顺便说一下,内部角度是这样做的引导程序模块:

      var injector = createInjector(modules, config.strictDi);
Run Code Online (Sandbox Code Playgroud)

createInjector 是一个角度的内部函数,它循环遍历所有模块并注册其所有各种构建块.

lazy.js中,我们调用$controllerProvider.register(..)懒惰注册我们的控制器.createInjector当应用程序被引导时,也会触发对同一函数的调用.以下是各种角度构建块的列表以及它们通过角度注册的方式:

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
Run Code Online (Sandbox Code Playgroud)

那么,有没有办法懒惰地实例化模块?是的,您可以通过迭代模块对象(requires_invokeQueue)的各种嵌套属性来注册模块及其子模块,该操作已在名为ocLazyLoad的库中进行了简化.

此部分中提供的大多数代码都可以在此plunker中找到.

(上面没有提到的灵感来源:Couch Potato,AngularAMD)


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

[ ocLazyLoad+ ui-router+ requirejs] - 插入

因为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'));
            });
          }
        }
      });
    ...
Run Code Online (Sandbox Code Playgroud)

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';
  });
});
Run Code Online (Sandbox Code Playgroud)

请注意,在延迟加载方面,此文件不再有任何特殊之处.你可以轻松地以非懒惰的方式加载这个文件,它不会知道区别.同样的原则适用于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>');
    });
});
Run Code Online (Sandbox Code Playgroud)

>>> 查看功能齐全的插件 <<<


部署

在部署时,这个难题的最后一部分是使用requirejs优化器来连接和最小化我们的js文件.理想情况下,我们希望优化器跳过已包含在主应用程序中的依赖项的连接(即:公共文件).要完成此操作,请参阅此repo及其随附的build.js文件.


更优雅的解决方案

我们可以通过添加ui-router装饰器来改进之前的插件,以获得非常优雅的解决方案.