Порядок загрузки в AngularJS приложении.

Введение.

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

Порядок загрузки блоков AngularJS.

При запуске приложения AngularJS блоки выполняются в следующем порядке:

  1. config()
  2. run()
  3. directive() (compile)
  4. controller()
  5. directive() (link)

Ссылка на живой пример.

Особенно стоит отметить особенность фабрик и сервисов (factory(), service()) — они могут вызываться как в блоке run(), так и в контроллере (controller), а также в директивах (directive()). Но не в блоке config().

Также есть разница в порядке загрузки директив в зависимости от возвращаемой внутри функции. На третьем этапе загрузки инициализируются как пользовательские, так и встроенные директивы найденные в DOM, но когда парсер AngularJS доходит до возвращаемой функции, то пока выполняются только compile функции пользовательских директив.

Потом выполняется код контроллера и только после этого парсер считывает возвращаемые функции link пользовательской директивы.

Подробнее про compile и link.

Пример для наглядности:

var angularApp = angular.module(‘angularApp', [‘additionalModules’]);

angularApp.directive("myDirectiveCompile", function() {

    console.log("Эта строка сработает после блока run() и до контроллера");c

    return {

        compile: function() {

console.log("Эта строка также сработает после блока run() и до контроллера, так как находится в возвращаемой функции compile");

}

    }

});

angularApp.controller('mainCtrl', [‘$scope’, function($scope) {

    console.log("Эта строка выполнится после всех директив и их возвращаемых функций compile.");

}]);

angularApp.directive("myDirectiveLink", function() {

console.log("Эта строка сработает после блока run() и до контроллера");

    return {

        link: function() {

console.log("Эта строка выполнится после контроллера mainCtrl.");

}

    }

});

Далее я приведу пример, как мне понадобилось использовать особенности порядка загрузки AngularJS приложения и применить еще некоторые плагины и инструменты данного фреймворка.

Практический пример с плагином SQLite в ionic приложении.

После запуска приложения у нас есть вид, в котором мы хотим вывести все данные, вынимаемые из базы данных. Если код для выгрузки данных из базы находится в контроллере, то пользователь будет небольшое время смотреть на пустую страницу, ждать когда подключится плагин SQLite, выполнится код контроллера и загрузятся все связанные $scope переменные.

Суть в том, что пока загружаются данные,  нужно показывать пользователю loader (индикатор загрузки). Это вполне стандартно и широко известно. Индикаторы загрузки используются абсолютно везде.

Можно было бы использовать индикатор загрузки приложения в контроллере, однако есть возможность получить ошибку, так как иногда на некоторых устройствах плагин SQLite загружается даже позже положенного, ну от этого есть еще небольшие минусы.

Для того чтобы данные приложения были гарантированно доступны еще до загрузки контроллера и человек соответственно сразу их увидел, в ionic приложении можно использовать resolve для state (или состояние, аналог активити в android приложении, в чистом AngularJS понадобится модуль ui router). Он находится в блоке config():

.config(['$stateProvider', '$urlRouterProvider', '$ionicConfigProvider', function ($stateProvider, $urlRouterProvider, $ionicConfigProvider) {

.state('appState', {

                resolve: {

                    Config: function (Config) {

                        return Config.all().then(function (data) {

                            console.log('Загрузился config приложения');

                            return data;

                        });

                    },

                    someData: function (someData) {

                        console.log(‘Загрузились данные приложения');

                        return someData.all();

                    }

                },

                url: '/app-state,

                templateUrl: 'templates/app-state.html',

                controller: 'AppStateCtrl'

            })

}]);

Все вроде правильно, но запустив приложение мы сталкиваемся с ошибкой. Именно на этом моменте нам пригодится знание порядка загрузки AngularJS приложения.

Ошибка кроется в том, что блок config() запускается раньше run(), но именно во втором блоке мы должны инициализировать имеющееся плагины (подробнее про run()). Решения два — первое все-таки сделать получение данных в контроллере, но перед этим проверять готовность плагина SQLite и показывать индикатор загрузки (loader). Второе, которое мы рассмотрим в данной статье, это сделать промежуточный экран с индикатором загрузки, при котором будут грузиться все нужные плагины, доставаться из базы нужная информация и может быть проводиться необходимые алгоритмы. Первое решение может быть рассмотрим как нибудь в следующий раз.

Итак, создаем новый state:

$stateProvider

            .state('loadscreen', {

                url: '/loadscreen',

                templateUrl: 'templates/load-screen.html',

                controller: 'LoadScreenCtrl'

            })

В LoadScreenCtrl контроллере ставим проверку, что как только загрузится все необходимое, то переходим на нужный state. Кстати в данном методе на этом этапе также удобно, что можно выбирать на какой state перейти в зависимости от многих условий. Например, в первый раз заходит человек или в настройках указал какую страницу приложения открывать первую и так далее.

Заключение

Таким образом, зная как происходит загрузка AngularJS приложения, можно оградиться от многих ошибок, например как в нашем примере с плагином SQLite. Надеюсь вам помогут эти знания и решение с экраном загрузки, которое было описано в данной статье. Спасибо за внимание! Если у вас есть вопросы — пишите в комментариях.