Модули в angularjs

Поговорим о повторно используемом коде. Чтобы код повторно использовать его нужно оформлять особым способом и выносить в пакеты. Сейчас все нормальные фреймворки в разных языках программирования имеют поддержку пакетов и пакетные менеджеры для управления ими, а так же для решения проблем с версиями и зависимостями. В javascript для управления пакетами применяется bower. Теперь же рассмотрим как писать код, чтобы его можно было внедрять в проекты, в качестве препарируемого будет angularjs.

Angularjs поддерживает модули, будем использовать именно их. Для примера можно разворатить пару готовых, я же постараюсь описать найденное мною. Модуль является контейнер в который мы можем складывать константы, переменные, контроллеры, сервисы и т.д., далее мы просто подключаем js файл и при создании модуля приложений добавляем свой модуль в зависимости.

angular.module('myModule', [])
  .value('bar', 123).;
angular
  .module('appModule', ['myModule'])

Теперь в модуле appModule будет доступно все из myModule, это довольно печальный факт, т.к. не продумали даже неймспейсов для избавления от коллизий с именованием. Т.е. переменная bar будет доступна именно по своему имени bar, хотелось бы что-то вроде myModule.bar. Потому в целях борьбы с этим старайтесь создавать модули на каждый чих, чтобы разом в скоуп не забрасывать все содержимое. Например, можно создавать модули отдельно для контроллеров, сервисов, директив или дробить модуль на части по функционалу.

angular.module('myModule.controllers', [])
  .controller(...)
  .controller(...);
angular.module('myModule.services', [])
  .service(...)
  .service(...);
angular.module('appModule', ['myModule.controllers', 'myModule.services'])
angular.module('myModule.blog', [])
  .controller(...)
  .service(...)
  .directive(...);
angular.module('myModule.catalog', [])
  .controller(...)
  .service(...)
  .directive(...);
angular.module('appModule', ['myModule.blog', 'myModule.catalog'])

К сожалению это всё что имеется у меня по данной теме, теперь покажу пример такого модуля. Как самый часто используемый я взял авторизацию и проверку прав. Набросал довольно простой модуль. Суть такова: при описании роутоу мы добавляем аттрибут access и если он есть, то проверяем залогинен ли пользователь. Для отлавливания момента смены роута вешаем событие на $rootScope.$on(‘$routeChangeStart’, …). Ну и пару методов для авторизации и её сброса.

(function (windows, angular) {
  'use strict';
  angular.module('amdyAuth', [])
    .factory('UserService', ['$rootScope', '$http', function ($rootScope, $http) {
      var self = {
        config: {
          urlLogin: '/login',
          urlLogout: '/logout',
          urlRedirect: '/'
        },
        isLogged: false,
        user: {},
        run: function (userConfig) {
          angular.extend(this.config, userConfig);
          $rootScope.$broadcast('UserService:run', this);
          $rootScope.$on('$routeChangeStart', function (event, next, current) {
            if (next.auth && !self.isLogged) {
              next.resolve = null;
              next.redirectTo = self.config.urlRedirect;
              event.preventDefault();
            }
          });
        },
        logout: function () {
          $rootScope.$broadcast('UserService:logout');
          this.clear();
          return $http.get(this.config.urlLogout);
        },
        clear: function () {
          this.isLogged = false;
          this.user = {};
        },
        login: function (data) {
          $rootScope.$broadcast('UserService:login', data);
          return $http.post(this.config.urlLogin, data)
            .success(function (data, status, headers, config) {
              self.isLogged = true;
              self.user = data;
              $rootScope.$broadcast('UserService:login:success', data);
            })
            .error(function (data, status, headers, config) {
              $rootScope.$broadcast('UserService:login:error', data);
            });
        }
      };
      return self;
    }]);
})(window, window.angular)

Как видно, создал модуль amdyAuth и в нем одну фабрику порождающий наш сервис UserService. Завёл переменную self, чтобы обращаться к объекту в callback-ах. Для конфигурирования создал конфиг, который затем можно переопределять при вызове метода run, где навешивается наше событие на смену роута. Так же для событий по смене состояний постоянно делаю broadcast, чтобы модуль можно было расширять без влезания в его код. Функционала минимум.

Теперь посмотрим его использование.

(function (windows, angular) {
  'use strict';
  angular.module('appModule', [
    'ngRoute',
    'amdyAuth'
  ]).config(['$routeProvider', '$locationProvider', '$httpProvider', function ($routeProvider, $locationProvider, $httpProvider) {
    $routeProvider.when('/', {controller: 'IndexController', templateUrl: '/app/_index.html'})
      .when('/public', {controller: 'PublicController', templateUrl: '/app/_public.html'})
      .when('/admin', {controller: 'AdminController', templateUrl: '/app/_admin.html', auth: true})
      .otherwise({redirectTo: '/'});
  }]).run(['UserService', '$rootScope', function (UserService, $rootScope) {
    $rootScope.$on('UserService:run', function (e, data) {
      console.info('UserService:run', data);
    });
    $rootScope.$on('UserService:login', function (e, data) {
      console.info('UserService:login', data);
    });
    $rootScope.$on('UserService:login:success', function (e, data) {
      console.info('UserService:login:success', data);
    });
    $rootScope.$on('UserService:login:error', function (e, data) {
      console.info('UserService:login:error', data);
    });
    $rootScope.$on('UserService:logout', function (e, data) {
      console.info('UserService:logout', data);
    });
    UserService.run({urlLogin: 'login.json', urlLogout: "login.json"});
  }]).controller('MainController', ['$scope', 'UserService', '$location', function ($scope, UserService, $location) {
    $scope.userService = UserService;
    $scope.logout = function () {
      UserService.logout();
      $location.path('/');
    }
  }]).controller('IndexController', ['$scope', 'UserService', function ($scope, UserService) {
    $scope.login = '';
    $scope.password = '';
    $scope.userService = UserService;
    $scope.doLogin = function () {
      UserService.login({login: $scope.login, password: $scope.password});
    }
  }]).controller('PublicController', [function () {
  }]).controller('AdminController', [function () {
  }])
  ;
})(window, window.angular);

Подключил свой модуль, после чего в методе run повесил слушетелей на наши события и вызвал метод UserService.run({urlLogin: ‘login.json’, urlLogout: «login.json»}); переопределяя переменные конфига. Для роута /admin указал что нужно проверять права auth: true.

Результат можно посмотреть забрать с гитхаба https://github.com/AmdY/blog-angular-modules

Теперь немного о модулях. Созданый модуль нужно выносить в отдельный репозиторий, добавлять в него bower.json и регистрировать, чтобы можно было использовать в других проектах. В попытках найти готовые модули я набрёл на один выживший проект http://ngmodules.org/. В больше тысячи зарегистрированных модулей, а вот статистика лайков удручает, у самого популярного всего 402, а с сотней не наберётся и десятка. Люди предпочитают велосипедить, хотя есть довольно полезные модули. Но при этом нормальной авторизации, который легко бы интегрировался с laravel я не нашёл, постараюсь докрутить до продакшен состояния этот.

p.s. Покритикуйте код, а то в javascript я далеко не гуру и стиль по старинке, как под IE6.

 

Модули в angularjs: 5 комментариев

  1. Владислав

    Зачем создавать переменную self, если все равно используется указатель this (вперемешку с self, что сбивает с толку)?

    1. AmdY Автор записи

      self используется в коллбэках, когда вешается какое-то событие или в promise для ajax запросов. В этих местах меняется контекст и this будет указывать на другие объекты, потому нужно либо использовать костыль с self, либо самому биндить нужный контекст.
      В остальных местах используется this, заменять его на self не виду смысла.

  2. Владислав

    Разобрался. Но все же, в таком случае, если можно использовать self, зачем писать this. Такое разнообразие указаний на один объект в одном и том же месте запутывает.

  3. Ярослав

    думаю, в данном примере лучше использовать service вместо factory, так как service — это инстанс, а factory — функция-конструктор

    1. AmdY Автор записи

      Да, очень ценное замечание, я как-то поимел проблем из-за любви к сервисам, вместо фабрик.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *