Как добавить require.js в мое приложение AngularJS?

У меня есть контроллеры в моем приложении 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

TL;DR; окончательное решение находится в последнем разделе или просто просмотрите этот фрагмент


Lazy Загрузка с помощью $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, которая дает ему доступ к фактическому контроллеру $scope и this, и позволяет ему получить доступ к любой из инъекций, загруженных в ваше приложение.

Этот метод можно применить к службе, factory или директиве.


Предостережения о ленивом загрузке

В большинстве случаев ленивая загрузка, вероятно, является самоуничтожением. Если ваша цель - дать вашим пользователям быстрый сайт, то ленивая загрузка каждого контроллера - плохая идея. Как только установлено соединение HTTP, большинство интернет-подключений позволяют передавать много данных через провод за короткий промежуток времени. Задержка, однако, может быть настоящим убийцей. Вот почему большинство сайтов в эти дни используют конкатенацию и минимизацию, чтобы упаковать их javascript и уменьшить количество сетевых запросов, а не полагаться на ленивую загрузку, которая увеличивает количество запросов.

Рассмотрим архитектуру вашего приложения. Сколько повторно используемых директив вы создадите? Сколько кода будет разделяться между различными частями приложения, непригодными для ленивой загрузки? Для многих приложений большая часть кода будет состоять из общих компонентов, делая ленивую загрузку бессмысленной.

Ленивая загрузка имеет смысл в приложении с очень четкими и отдельными фрагментами. Части, которые настолько раздельны и раздельны, их можно рассматривать как отдельные приложения. Однако даже в этом случае вы можете рассмотреть возможность создания отдельных приложений, а не комбинирования их.


BTW, require.js по-прежнему полезен, даже если вы не Lazy-loading

Даже если вы не ленивы, require.js чрезвычайно полезен для управление зависимостями. Используется вместе с require.js Оптимизатор - это элегантный способ отслеживания зависимостей и сжать + уменьшить ваше приложение.

Вы также можете использовать require.js для загрузки зависимостей для запуска Jasmine модульные тесты, которые помогают поддерживать модульность компонентов и ускоряются ваши тесты, просто загружая нужные вам зависимости. Для единицы я создаю отдельный файл main-test.js, который вызывает require.config(...) для загрузки зависимостей приложений, а также зависимые от тестирования зависимости.



Архитектура ленивой загрузки

Lazy-load с angular довольно сложна, потому что angular не предназначен для поддержки ленивой загрузки. В этом разделе я попытаюсь привлечь вас к исследованию о том, как можно заставить angular поддерживать ленивую загрузку. Это не полномасштабное решение, но я надеюсь представить концепции, которые необходимо понимать при создании такого приложения.

Начнем с маршрутизатора, в отличие от angular -requirejs-seed, представленного в первом разделе, на самом деле имеет смысл сделать более ленивым загрузку для работы в вашем маршрутизаторе приложений. Используя ui-router, мы можем реализовать lazy-load таким образом:

...
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). Кроме того, обратите внимание, что мы загружаем частичный вид view, lazy.html непосредственно из $templateCache. То есть, когда мы загрузили lazy.js, частичное было включено с lazy.js. Теоретически мы могли бы просто загрузить lazy.html отдельно от lazy.js, но для лучшей производительности мы должны скомпилировать частичные файлы в наши 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);
});

Имейте в виду, что приведенный выше код представляет собой uncompiled 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 будет автоматически генерироваться автоматически из ваших html файлов с помощью плагина инструмента сборки, такого как grunt-html2js.

Теперь вы могли бы теоретически построить все свое приложение, используя представленный до сих пор подход. Впрочем, это немного... язвительно. То, что мы бы очень хотели сделать в нашем lazy.js, - это создать новый модуль, например appLazy = angular.module('app.lazy'), а затем создать экземпляр нашего контроллера, директив, служб и т.д., Например appLazy.directive(...).

Однако причина, по которой мы не можем сделать этого, состоит в том, что все эти вещи инициализируются (и доступны для нашего приложения) в методе angular.bootstrap, который уже был вызван временем загрузки lazy.js. И мы не можем просто вызвать angular.bootstrap(...) снова.


BTW, внутренне angular делает это для загрузки модулей:

      var injector = createInjector(modules, config.strictDi);

createInjector является внутренней функцией angular, которая проходит через все модули и регистрирует все их различные строительные блоки.

В lazy.js мы вызвали $controllerProvider.register(..) в лениво зарегистрируйте наш контроллер. createInjector также запускает вызов той же функции, когда приложение загружается. Вот список различных angular строительных блоков и способа, которыми они являются зарегистрировано 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

Итак, есть ли способ лениво создавать экземпляры модулей? Да, вы можете зарегистрировать модуль и его подмодули путем итерации через различные вложенные свойства объекта модуля (requires и _invokeQueue), операция, которая была упрощена в lib под названием ocLazyLoad.

Большая часть кода, представленного в этом разделе, доступна в этом плунжере.

(Источники вдохновения, не упомянутые выше: Картофель-кушетка, AngularAMD)


Полное решение для ленивой загрузки:

[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>');
    });
});

→ > проверить полностью функционирующий планшет < <


Развертывание

Когда дело доходит до развертывания, заключительной частью этой головоломки является использование оптимизатора requirejs для объединения и минимизации наших js файлов. В идеале мы хотим, чтобы оптимизатор пропускал конкатенацию зависимостей, которые уже включены в основное приложение (то есть: общие файлы). Для этого см. Это репо, и оно сопровождает файл build.js.


Еще более элегантное решение

Мы можем улучшить предыдущую панель, добавив декоратор ui-router для очень элегантного решения.