Пользователь
0,0
рейтинг
16 августа 2013 в 16:42

Разработка → Понимание типов сервисов в AngularJS (constant, value, factory, service, provider) перевод tutorial

Ангуляр поставляется с различными видами служб или сервисов, каждый из которых применяется в своей ситуации.
Имейте в виду, что сервисы, не зависимо от типа, это всегда синглтоны (одиночки).

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

Перейдем к типам сервисов

Constant


app.constant('fooConfig', {
  config1: true,
  config2: "Default config2"
});

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

Значение константы задается при определении и не может быть изменено другим путем. Значением константы может быть примитив или объект. Так же константа может настраиваться на config стадии модуля. Value же сможет быть использована только на стадии run и далее (прим. из комментариев).

Value


app.value('fooConfig', {
  config1: true,
  config2: "Default config2 but it can changes"
});

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

Factory


app.factory('foo', function() {
  var thisIsPrivate = "Private";
  function getPrivate() {
    return thisIsPrivate;
  }

  return {
    variable: "This is public",
    getPrivate: getPrivate
  };
});

// или...

app.factory('bar', function(a) {
  return a * 2;
});

Фабрика является наиболее часто используемым сервисом. Так же она самая простая для понимания.

Фабрика это сервис, который может вернуть любой тип данных. Она не содержит правил по созданию этих данных. Нужно всего лишь вернуть что-то. При работе с объектами, мне нравится работать с шаблоном открытого модуля, но вы можете использовать другой подход, если хотите.

Как упоминал выше, все типы это синглтоны, так что, если мы изменим foo.variable в одном месте, в других местах она тоже изменится.

Service


app.service('foo', function() {
  var thisIsPrivate = "Private";
  this.variable = "This is public";
  this.getPrivate = function() {
    return thisIsPrivate;
  };
});

Сервис (не путайте общее название с конкретным типом) работает так же как фабрика. Разница в том, что сервис использует конструктор, поэтому, когда используете его в первый раз, он выполнит new Foo(); для создания экземпляра объекта. Имейте в виду, что этот же объект вернется и в других местах, если использовать там этот сервис.

Фактически сервис эквивалентен следующему коду:

app.factory('foo2', function() {
  return new Foobar();
});

function Foobar() {
  var thisIsPrivate = "Private";
  this.variable = "This is public";
  this.getPrivate = function() {
    return thisIsPrivate;
  };
}

Foobar является классом, и мы создаем его экземпляр на нашей фабрике, используем его первый раз, а затем возвращаем. Как и сервис, экземпляр класса Foobar будет создан только однажды и в следующий раз фабрика вернет этот же экземпляр снова.

Если у нас уже есть класс, и мы хотим использовать его в нашем сервисе, можем сделать так:

app.service('foo3', Foobar);

Provider


Провайдер это фабрика, настроенная особым образом. Фактически фабрика из последних примеров будет выглядеть как-то так:

app.provider('foo', function() {

  return {

    $get: function() {
      var thisIsPrivate = "Private";
      function getPrivate() {
        return thisIsPrivate;
      }

      return {
        variable: "This is public",
        getPrivate: getPrivate
      };
    }

  };

});

Провайдер ожидает функцию $get, которая будет тем, что мы внедряем в другие части нашего приложения. Поэтому, когда мы внедряем foo в контроллер, то внедряется функция $get

Почему нужно использовать эту форму, когда фабрика гораздо проще? Потому что провайдер можно настроить в конфигурационной функции. Так что можно сделать что-то вроде этого:

app.provider('foo', function() {

  var thisIsPrivate = "Private";

  return {

    setPrivate: function(newVal) {
      thisIsPrivate = newVal;
    },

    $get: function() {
      function getPrivate() {
        return thisIsPrivate;
      }

      return {
        variable: "This is public",
        getPrivate: getPrivate
      };
    }

  };

});

app.config(function(fooProvider) {
  fooProvider.setPrivate('New value from config');
});

Здесь мы вынесли thisIsPrivate за пределы функции $get, а затем создали функцию setPrivate, чтобы иметь возможность изменить thisIsPrivate в функции конфигурации. Почему нужно это делать? Не проще ли просто добавить сеттер на фабрике? Тут другая цель.

Мы хотим внедрить определенный объект, но хотим иметь способ настроить его для своих нужд. Например: для сервиса-обертки ресурса, использующего JSONP, хотим иметь возможность настроить URL, который будет использоваться нами или сторонними сервисами, такими как restangular. Провайдер позволяет нам настроить предварительно его для наших целей.

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

Видя это, вспоминаем, что уже настраивали некоторые сервисы в наших приложениях, например, в $routeProvider и $locationProvider настраивают роутинг и html5mode соответственно.

Бонус 1: Декоратор


Итак, вы решили, что какому-то сервису foo, не хватает функции greet и вы хотите ее добавить. Стоит ли изменять фабрику? Нет! Можно ее декорировать:

app.config(function($provide) {
  $provide.decorator('foo', function($delegate) {
    $delegate.greet = function() {
      return "Hello, I am a new function of 'foo'";
    };

    return $delegate;
  });
});

$provide это то, что Ангуляр использует для создания всех внутренних сервисов. Мы можем использовать его вручную, если хотим, или просто использовать функции, предоставляемые в наших модулях (необходимо использовать $provide для декорирования). $provide имеет функцию, decorator, которая позволяет нам декорировать наши сервисы. Она получает имя декорируемого сервиса, а колбэк получает $delegate, который является оригинальным экземпляром сервиса.

Здесь мы можем делать все что захотим хотим, чтобы декорировать наш сервис. В нашем случае, мы добавили функцию greet в оригинальный сервис. Затем вернули новый модифицированный сервис.

Теперь, при использовании, он будет иметь новую функцию greet.

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

Примечание: Константу декорировать нельзя.

Бонус 2: создание новых экземпляров


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

// Наш класс
function Person( json ) {
  angular.extend(this, json);
}

Person.prototype = {
  update: function() {
    // Обновляем (В реальном коде :P)
    this.name = "Dave";
    this.country = "Canada";
  }
};

Person.getById = function( id ) {
  // Делаем что-то, чтобы получить Person по id
  return new Person({
    name: "Jesus",
    country: "Spain"
  });
};

// Наша фабрика
app.factory('personService', function() {
  return {
    getById: Person.getById
  };
});

Здесь создаем объект Person, который получает некоторые JSON данные для инициализации объекта. Затем создали функцию в нашем прототипе (функции в прототипе для экземпляров Person) и функции непосредственно в Person (подобные функции класса).

Так что у нас есть функция класса, которая создаст новый объект Person на основе идентификатора, который мы передадим (так будет в реальном коде) и каждый экземпляр сможет обновлять сам себя. Теперь просто нужно создать сервис, который будет его использовать.

Каждый раз, когда мы вызываем personService.getById мы создаем новый объект Person, так что можно использовать этот сервис в различных контроллерах и даже тогда, когда фабрика является синглтоном, она создает новые объекты.

Респект Джошу Дэвиду Миллеру за его пример.

Бонус 3: CoffeeScript


CoffeeScript можно удобно сочетать с сервисами, поскольку они обеспечивают более симпатичный способ создания классов. Давайте рассмотрим Бонусный пример 2 с использованием CoffeeScript:

app.controller 'MainCtrl', ($scope, personService) ->
  $scope.aPerson = personService.getById(1)

app.controller 'SecondCtrl', ($scope, personService) ->
  $scope.aPerson = personService.getById(2)
  $scope.updateIt = () ->
    $scope.aPerson.update()

class Person

  constructor: (json) ->
    angular.extend @, json

  update: () ->
    @name = "Dave"
    @country = "Canada"

  @getById: (id) ->
    new Person
      name: "Jesus"
      country: "Spain"

app.factory 'personService', () ->
  {
    getById: Person.getById
  }

Сейчас он выглядит красивее, по моему скромному мнению.
Сейчас он выглядит ужаснее по скромному мнению переводчика.

Заключение


Сервисы являются одними из самых привлекательных функций Ангуляра. Существует множество способов их создания, нужно всего лишь выбрать правильный лучше всего подходящий в нашем случае
Перевод: Jesus Rodriguez
Олег Истомин @tamtakoe
карма
60,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (37)

  • +1
    Я недавно начал использовать Angular, зачастую пишу на CoffeeScript так:

    angular.module('my-app.models').factory 'MyModel', ->
      class MyModel
        name: null
        setName: (@name) ->
        getName: -> @name
    


    Имеет ли такой подход право на жизнь, или лучше заменить фабрикой, без прямого достпа к модели?
    • +2
      Интуиция мне подсказывает, что вы хотите нечто вроде Resource: http://docs.angularjs.org/api/ngResource.$resource
      А если для тестирования локально, то HttpBackend: http://docs.angularjs.org/api/ngMockE2E.$httpBackend

      А, вообще, подход, конечно, имеет право на жизнь. Но мне кажется он концептуально не в духе Angular. Энгьюла все-таки сделан не в духе ООП, который нам пытается подсовывать Кофискрипт.
      • 0
        Немного уточню: модель здесь в первую очередь реализует бизнес-логику, она не обязательно обменивается данными с сервером напрямую.
        • +1
          Имеет ли такой подход право на жизнь, или лучше заменить фабрикой, без прямого достпа к модели?

          Как душе удобно.

          Насчёт моделей в целом:
          Я реализовывал нормальную модель, которая наследовалась от Model, которая в свою очередь имела 2 реализации: серверная и клиентская. Клиентская отправляла данные на сервер, серверная — формировала sql запрос и отправляла запрос бд. Валидация моделей осуществлялась через фильтры, таким образом разработка клиент-сервер приложения сильно упростилась — данные на сервере валидировались прозрачно.
  • +2
    На мой взгляд, тут не везде правильно расставлены акценты.
    Constant не означает, что ее нельзя изменять. Особенность константы, что ее можно использовать на config стадии модуля. Value же сможет быть использована только на стадии run и далее. Т.е. тут больше семантики, чем реальных ограничений на изменение.
    • 0
      Хорошее замечание. Дополнил статью
    • –1
      Помимо этого константы позволяют добавлять в кэш любые объекты, а не только провайдеры ( со всеми вытекающими ).

      А вообще я уже не понимаю почему по AngularJS такие глупые статьи, которые, по сути, повторяют документацию, ведь сложность ангуляра совсем не в этом…
      • 0
        Обычная тема, в которой достаточно часто путаются. Непонятно почему статья глупая. Не хотите — не читайте или даже минус ставьте. Или напишите умную про то, в чем сложность AngularJS.
        • –1
          Я не говорю что против таких статей.

          Непонятно почему статья глупая.

          Потому что она не несёт ничего нового, всё это написано в документации.
          • +2
            Да почти все написано либо в документации, либо в исходниках. API-документация хороша, когда уже точно знаешь, что искать. Здесь материал по-другому структурирован и приведены примеры.

            Возьмем в пример, provider. Смотрим на документацию angular.Module#provider:
            See $provide.provider().


            Хмм, ок. Смотрим $provide.provider():
            Register a provider for a service. The providers can be retrieved and can have additional configuration methods.


            Когда это все использовал, знаешь, в голове все уже хорошо уложено и структурировано, в принципе, и достаточно. Когда, только разбираешься со всем этим. Лучше бы примеры, да сравнения рядом разных подходов. Что и есть в данной статье.
            • 0
              Ну нужно знать что такое провайдер вообще ( что интересно многие не понимают именно этого ), а потом залезть в исходник и понять что вские factory это лишь обёртки.
              • 0
                Это тоже полезный путь. Для понимания сути реализации. И одно другому не мешает, а только помогает структурировать знания с разных точек зрения.
      • +1
        Потому что на русском даже таких статей нет. Надо же с основ начинать. В документации, к сожалению, не сказано как и зачем применять разные типы сервисов и человек не знакомый с паттернами проектирования просто не поймет зачем они нужны.

        И в чем, по вашему, сложность Ангуляра?
        • 0
          Потому что на русском даже таких статей нет.

          angular.ru

          разные типы сервисов

          Какие типы?

          И в чем, по вашему, сложность Ангуляра?

          Например связь директив
          • 0
            Какие типы?

            constant, value, factory, service, provider — типы сервисов.

            Представьте строительную технику, машины с ковшами, подъемниками и проч. и техническую документацию для нее, где указаны: ход ковша 2,2 м, угол разворота 270°, грузоподъемность 1,8 т. Эти данные ничего не говорят о том, для чего эта техника нужна. Поэтому в документации всегда есть пункт «назначение», где указано: применяется для рытья канав, глубиной до 1 м. Вот с «назначением» в ангуляровской документации не очень хорошо.

            Например связь директив


            А в чем там проблемы? На одном и том же элементы директивы связываются так: www.egghead.io/video/rzMrBIVuxgM, на разных — по событиям или через область видимости.
            • 0
              А в чем там проблемы?

              А, т.е. связь директив со всякими функциями линковки, scope связей и т. д. для Вас раз плюнуть, а работа сервисов, где, по сути, value == factory == service == provider это сверх трудно для понимания, и нужно писать по 100 статей на эту тему?
              • 0
                Обычно перевожу статьи, объясняющие какие-то сложные моменты, которые меня интересуют (всё равно ковыряюсь в теме, так чего бы не потратить 2 часа и не порадовать Хабр). Или статьи (как эту) объясняющие как и в каких ситуациях пользоваться тем или иным функционалом.

                Про связь между директивами у меня нет никакого представления зачем это нужно. Один раз была необходимость сообщать о том, что происходит внутри директивы и просто эмитил событие до корневой области видимости, а в другом месте внедрял $rootScope и вешал на него обработчик нужного события. Не писать же статью, основываясь на одном случае использования, не рассмотрев другие варианты.

                value == factory == service == provider — это очень трудно для понимания! Возьмите пример из моей соседней статьи habrahabr.ru/post/190370/. Правильно ли там использовать фабрику? Может быть провайдер? Может вовсе в директиву запихнуть?
              • +1
                Ну как бы, по-крайней мере семантически, value != factory != service != provider — в этом и смысл. Тут бы впору, либо создателей поругать, зачем наплодили методов для одного и того же, либо все же разницу разъяснить.
              • 0
                Вы имеете в виду особенности поведения scope внутри директивы (isolate, child и scope контроллера, внутри которого эта директива используется)?
  • 0
    не туда написал…
  • 0
    Обычно перевожу статьи, объясняющие какие-то сложные моменты, которые меня интересуют (всё равно ковыряюсь в теме, так чего бы не потратить 2 часа и не порадовать Хабр). Или статьи (как эту) объясняющие как и в каких ситуациях пользоваться тем или иным функционалом.

    Я ничего не имею против таких статей, но хотелось бы статьи про редкие, но сложные моменты.

    Не писать же статью, основываясь на одном случае использования, не рассмотрев другие варианты.

    Как раз такое довольно интересно читать.

    Правильно ли там использовать фабрику? Может быть провайдер? Может вовсе в директиву запихнуть?

    Тут нужно просто понимать что такое провайдер, и зачем он вообще нужен.
    • 0
      Как раз такое довольно интересно читать.

      Попробую как нибудь, проверю сколько плюсов соберет)

      Тут нужно просто понимать что такое провайдер, и зачем он вообще нужен.

      Что такое провайдер и зачем он, вообще, нужен? В моем понимании, он нужен только для того, чтобы можно было задать настройки по умолчанию. Своего рода смесь фабрики с константой. А зачем нужен сервис, вообще, не пойму. Ну экземпляр возвращает ну и что?
      • 0
        Как я понимаю провайдер нужен для конфигурации сервиса в целом, именно тогда его и нужно использовать.

        app.provider( 'foo', function()
        {
        	var scale = 1;
        
        	// Методы для конфигурирования сервиса.
        	this.setScale = function( scale_ ) { scale = scale_; };
        	// ...
        
        	// Сам сервис.
        	this.$get = {
        
        		getScale : function() { return scale; },
        
        		scale : function( n ) { return n * scale }
        	};
        } );
        
        app.config(function(fooProvider)
        {	
        	// Конфигурируем сервис.
        	fooProvider.setScale(2);
        });
        


        factory, service — объявления сервиса, их используем когда сервис конфигурировать не нужно.

        Про value, constant рассказал aav, добавлю что объект добавленный через constant не имеет провайдера и его нельзя декорировать ( вот в этом плане говорят про его «неизменность» ).
        • 0
          Ну это смотря, конечно, что подразумевать под провайдером. У константы просто название провайдера без суффикса «Provider», поэтому decorator просто не сможет ее получить. Да и смысла в декорировании константы особого нет. Ее и так можно проинжектить в config и сделать с ней все что хочешь.
          • 0
            Стандартный пример настройки провайдера:
            $routeProvider.
            when('/phones', {templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl}).
            when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
            otherwise({redirectTo: '/phones'});

            Почему бы не задавать настройки через переменную, например? Внедряешь и настраиваешь все что хочешь
            value('$config', {
            $route: {
            when: [
            {url: '/phones', templateUrl: 'partials/phone-list.html', controller: PhoneListCtrl},
            {url: '/phones/:phoneId', templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}
            ],
            otherwise: {redirectTo: '/phones'}
            }
            }})

            Зачем ввели новую сущность: провайдер?
            • 0
              Зачем ввели новую сущность: провайдер?

              Потому что у сервиса, сделанного через factory, нет доступа к секции config.

              Как я думаю, это сделано ради легкого конфигурирования и разделения кода.

              Нужно использовать только дефолтные настройки? Пожалуйста.
              Нужно в данном конкретном приложении настроить этот провайдер вот таким образом? На здоровье.

              Почему бы не задавать настройки через переменную, например? Внедряешь и настраиваешь все что хочешь

              В данном конкретном случае fluent предпочтительней такого монструозного конфига. Код более структурирован и понятен.
        • 0
          Т.е. константа — это не провайдер в том, смысле, что она не предоставляет тот же интерфейс с методом $get. Но константа — провайдер с той точки зрения, что лежит в providerCache.
          • 0
            Ну с точки зрения AngularJS — провайдер это интерфейс с обязательным методом $get.
            • 0
              Но в providerCache рядом с такими провайдерами лежат и объекты, не реализующие такой интерфейс, как ни крути :)
              • 0
                Ну это уже к разработчикам, я вообще против этих провайдеров.
  • 0
    Провайдер это фабрика, настроенная особым образом

    тут либо опечатка вышла либо недопонимание, ведь на самом деле фабрика — это частный случай провайдера.
    тут куски кода привел для наглядности.

    и вот в дополнение табличка что куда можно инжектить:
    image
    • 0
      У Вас ошибочная табличка. Провайдер в конфиг инжектится.
      • 0
        вы сейчас говорите о нативных провайдерах или кастомных?

        если нативных — полностью согласен.
        если о кастомных — покажите пожалуйста пример кода.
        • 0
          Имел в виду кастомные, проверил — был неправ.
        • 0
          Таблица может вызвать некоторую путаницу. Она про сервисы, создаваемые с помощью разных способов? Просто провайдер сервиса то в .config заинжектится, иначе в чем бы был смысл. Сам инстанс сервиса, естественно, нет.

          Под инъекцией в провайдер тут не очень понятно, что подразумевается — инъекции в $get? Там же инъекции в providerFn вобщем-то возможны.
  • 0
    Изучаю AngularJS и не смог понять сути, что такое фабрика, сервис и провайдеры, читая эту статью.
    Разобрался почему — вещи названы не своими именами, примеры:

    «Фабрика это сервис...»

    Это не так.
    .factory — это не сервис.
    .factory — это рецепт (синтаксический сахар), который создает новый сервис из переданной функции.
    Источник: docs.angularjs.org/guide/providers

    Почему это очень путает новичка? Потому что он видит утверждение «Фабрика — это сервис».
    Запоминает.
    И потом по ходу вникания в примеры не может сопоставить информацию, обнаруживая противоречия —
    так фабрика это сервис или это метод, который создает для нас сервис (привычное понимание фабрики в ООП)

    «Провайдер это фабрика, настроенная особым образом.»

    Это уже радикальное искажение информации.

    Провайдер — это объект, который поставляет экземпляр сервиса:
    Providers are objects that provide (create) instances of services and expose configuration APIs that can be used to control the creation and runtime behavior of a service. In case of the $route service, the $routeProvider exposes APIs that allow you to define routes for your application.

    Источник: docs.angularjs.org/tutorial/step_07

    После повторного прочтения этой статьи (когда я разобрался в реальном положении вещей и нативной документации),
    я вижу, что в описанном есть смысл — автор пытается, упрощая, объяснить суть,
    но в итоге здесь информация так искажена, что будет понятна только тому, кто уже разбирается в сути (тогда он мысленно поправит ошибки).

    Упрощать информацию — это благая цель. Но не искажайте ее, пожалуйста.
    Во имя тех, кто верит хабру и будет пытаться разобраться по вашим статьям,
    а не по оригинальной длиной документации.
    • 0
      Так это перевод :-) Ты еще не видел мой пост где объясняю разницу на пальцах. Там совсем упрощено всё.

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