html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

AngularJs. Отложенная загрузка модулей

AngularJs – великолепный фреймворк для разработки web-приложений. Разработка бизнес-логики приложения полностью отделена от сопутствующей суеты вокруг DOM. Angular модульный – это замечательно, но так же является источником проблемы. Количество модулей быстро растёт. И если директивы ещё можно упаковывать в отдельные пакеты типа angular-ui, то с контроллёрами бизнес-логики всё сложнее. Всё становится ещё хуже, когда требования безопасности в принципе запрещают загрузку на клиента контроллёров с бизнес-логикой, которые недоступны текущему пользователю. При развитой ролевой системе доступа к приложению масштаб проблемы становится очевиден.

В Angular в принципе отсутствует система загрузки модулей по требованию. Но тем не менее можно самостоятельно разработать такой модуль, который будет подгружать javascript-файл. Но есть проблема. При вызове функции angular.module c которой начинается любой модуль Angular, не приводит к добавлению функционала во внутренние структуры Angular. И сделано это намеренно, чтобы можно было указывать тэги script в произвольном порядке не соблюдая зависимостей между модулями. Окончательная загрузка модулей будет произведена после того как html-документ будет полностью загружен. Собственно этим и занимается функция angular.bootstrap, которая создаёт экземпляр injector`а и инициализирует внутренние структуры фреймворка.

Итак, возникает задача:
  1. Обеспечить загрузку модулей с помощью директивы. Это даст возможность загружать модуль именно тогда, когда он действительно необходим.
  2. Обеспечить разрешение зависимостей. Т.е. если модуль имеет зависимости, то проверить все ли они удовлетворены. И если нет – то инициировать процедуру загрузки модулей, удовлетворяющих зависимость.
  3. Директива так же должна обеспечивать загрузку указанного шаблона, поскольку директивы в шаблоне могут иметь зависимость от загружаемого модуля (например, указание контроллёра) и модуль должен быть загружен раньше, а только потом применён шаблон.
  4. Ну и, естественно, компиляция и линковка загруженного шаблона.

Приступим.
Пример директивы, появление которой в коде будет инициировать загрузку модуля home:
<div load-on-demand="'home'"></div>

Помимо самой директивы load-on-demand, имеется имя загружаемого модуля. Такой вариант выбран для большей гибкости в конфигурировании загружаемых модулей. Конфигурирование обычно производится с помощью вызова функции module.config.
Пример вызова функции:
var app = angular.module('app', ['loadOnDemand']);
app.config(['$loadOnDemandProvider', function ($loadOnDemandProvider) {
    var modules = [
        {
            name: 'home',
            script: 'js/home.js'
        }
    ];
    $loadOnDemandProvider.config(modules, []);
}]);


Теперь перейдём непосредственно к директиве. В нашем случае нам не требуется тонко настраивать директиву, поэтому мы возвращаем только функцию связывания (linkFunction), которая делает всё необходимое. Псевдо-код, который демонстрирует алгоритм:
var aModule = angular.module('loadOnDemand', []);
aModule.directive('loadOnDemand', ['$loadOnDemand', '$compile',
        function ($loadOnDemand, $compile) {
            return {
                link: function (scope, element, attr) {
                    var moduleName = scope.$eval(attr.loadOnDemand); // Имя модуля
                    // Получаем конфигурационную информацию о модуле
                    var moduleConfig = $loadOnDemand.getConfig(moduleName); 
                    $loadOnDemand.load(moduleName, function() { // Загружаем скрипт
                        loadTemplate(moduleConfig.template, function(template) { // Загружаем шаблон
                             childScope = scope.$new(); // Создаём область видимости для контроллёра
                             element.html(template); // Вставляем сырой html в DOM
                             var content = element.contents(),
                             linkFn = $compile(content); // Преобразуем DOM-узел в шаблон angular
                             linkFn(childScope); // Связываем шаблон и scope
                         });
                    });
                }
            };
        }]);


Ключевым моментом здесь является вызов функции $loadOnDemand.load(). Весь функционал по конфигурированию и загрузки скрипта находится в провайдере $loadOnDemand. Раскроем его. Я намеренно скрываю детали реализации, чтобы не захламлять код.

aModule.provider('$loadOnDemand', function(){
    this.$get = [function(){ // Обязательная для провайдера функция, которая будет возвращать сервис
        return {
            getConfig: function (name) {}, // Получение конфигурации для загрузки модуля
            load: function (name, callback) {} // Загрузка модуля
        };
    }];
    this.config = function (config, registeredModules) {} // Функция конфигурирования провайдера
});


Каждый провайдер должен предоставить функцию $get, которая должна возвращать объект-сервис. Этот сервис будет использоваться инектором, когда он потребуется. Помимо функции $get наш привайдер предоставляет функцию config — она используется для конфигурирования загрузчика модулей (app.config выше). Дело в том, что функция module.config предоставляет только провайдеров, поэтому необходимо разделить логику конфигурирования провайдера от предоставляемого им сервиса.
Сам сервис имеет две функции: getConfig — используется для простоты получения конфигурационного объекта и, собственно, главная функция сервиса — load, которая загружает модуль. Низкоуровневая загрузка скрипта выполняется с помощью document.createScript — такая загрузка более дружественна для IDE отладчика.

И воде бы — это и всё, что нужно сделать. Но, это не будет работать. Причина указана выше — после того как скрипт будет загружен и выполнен, функционал модуля не будет размещён в инфраструктуре angular. Итак, погружаемся в angular.bootstrap.

После того как DOM загружен, запускается процедура инициализации angular. Она ищет директиву ng-app с именем главного модуля приложения. После этого создаётся инектор и выполняется компиляция DOM в шаблон angular`а. В этой цепочке нас больше всего интересует создание инектора, поскольку именно этот вызов запускает процедуру загрузки модулей — функцию loadModules. loadModules получает объект Module в котором имеется очередь команд для инектора — _invokeQueue. Эта очередь как раз и создаётся при вызове angular.module. Каждый элемент этой очереди отдаётся соответствующему провайдеру, который делает всю работу по добавлению функционала.
Нам необходимо просто повторить этот алгоритм, используя уже существующие провайдеры. Их мы получаем используя инектор.
aModule.provider('$loadOnDemand',
        ['$controllerProvider', '$provide', '$compileProvider', '$filterProvider',
            function ($controllerProvider, $provide, $compileProvider, $filterProvider) {
                . . .
                loadScript(moduleName, function(){
                    register(moduleName);
                });
                . . .
            }]);

Функция регистрации модуля register.
moduleFn = angular.module(moduleName);
for (invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
  invokeArgs = invokeQueue[i];
  provider = providers[invokeArgs[0]]; 
  provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}


В invokeArgs[0] находится имя провайдера, invokeArgs[1] — его метод регистрации нового сервиса. invokeArgs[2] — параметры, которые передаются методу регистрации (список инекций и функция-конструктор сервиса).

Вот, пожалуй и всё, остаётся только загрузить зависимости, которые находятся в moduleFn.requires в виде простого массива имён модулей. После подключения подобного модуля к вашему проекту главная страница будет выглядеть как-то так:
<!DOCTYPE html>
<html ng-app="app">
<head>
</head>
<body>
    <div ng-view></div>

    <script src="js/angular.js"></script>    
    <script src="js/loadOnDemand.js"></script>
</body>
</html>

А главный модуль приложения, как-то так:
(function(){
    var app = angular.module('app', ['loadOnDemand']);
    app.config(['$routeProvider', function ($routeProvider) {
    . . .
    };
    app.config(['$loadOnDemandProvider', function ($loadOnDemandProvider) {
    . . .
    };
})();


Проект лежит на github с демонстрационным примером
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

1

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции