Синглтон сервис angular что это
Angular Singleton Service
Предоставление единого сервиса
В Angular существует два способа сделать сервис одиночным:
Использование предоставлено
Начиная с Angular 6.0, предпочтительным способом создания одноэлементного сервиса является задание в корневом каталоге обеспеченного входа в декораторе @Injectable () службы. Это говорит Angular о предоставлении сервиса в корне приложения.
Массив провайдеров NgModule
В приложениях, созданных с версиями Angular до 6.0, сервисы регистрируются массивами провайдеров NgModule следующим образом:
Если бы этот NgModule был корневым AppModule, UserService был бы единичным и доступным по всему приложению. Хотя вы можете видеть, что это закодировано таким образом, использование свойстваIn в декораторе @Injectable () для самого сервиса предпочтительнее, чем в Angular 6.0, поскольку оно делает ваши сервисы доступными для дерева.
Шаблон forRoot ()
Как правило, вам понадобится только предоставленный для предоставления услуг и forRoot () / forChild () для маршрутизации. Однако понимание того, как работает forRoot (), чтобы убедиться, что служба является одноэлементной, проинформирует вашу разработку на более глубоком уровне.
Если модуль определяет как поставщиков, так и декларации (компоненты, директивы, каналы), загрузка модуля в несколько функциональных модулей дублирует регистрацию службы. Это может привести к нескольким экземплярам службы, и служба больше не будет работать как одиночная.
Есть несколько способов предотвратить это:
Используйте forRoot () для отделения поставщиков от модуля, чтобы вы могли импортировать этот модуль в корневой модуль с поставщиками и дочерними модулями без поставщиков.
Greeting.module.ts
forRoot () и роутер
RouterModule предоставляет сервис Router, а также директивы маршрутизатора, такие как RouterOutlet и routerLink. Модуль корневого приложения импортирует RouterModule, чтобы приложение имело Маршрутизатор, а компоненты корневого приложения могли получить доступ к директивам маршрутизатора. Любые функциональные модули также должны импортировать RouterModule, чтобы их компоненты могли помещать директивы маршрутизатора в свои шаблоны.
Понимание типов сервисов в AngularJS (constant, value, factory, service, provider)
Ангуляр поставляется с различными видами служб или сервисов, каждый из которых применяется в своей ситуации.
Имейте в виду, что сервисы, не зависимо от типа, это всегда синглтоны (одиночки).
Примечание: Синглтон это шаблон проектирования, который ограничивает класс таким образом, что у него может быть только один экземпляр. Именно с этим экземпляром и ведется работа везде, где он используется.
Перейдем к типам сервисов
Constant
Константа часто используется для конфигурации по умолчанию в директивах. Так что если создаете директиву, и хотите помимо настройки иметь возможность передать ей некоторые стандартные параметры, константа — хороший способ сделать это.
Значение константы задается при определении и не может быть изменено другим путем. Значением константы может быть примитив или объект. Так же константа может настраиваться на config стадии модуля. Value же сможет быть использована только на стадии run и далее (прим. из комментариев).
Value
Переменная подобна константе, но может быть изменена. Она часто используется для настройки директив. Переменная подобна усеченной версии фабрики, только содержит значения, которые не могут быть вычислены в самом сервисе.
Factory
Фабрика является наиболее часто используемым сервисом. Так же она самая простая для понимания.
Фабрика это сервис, который может вернуть любой тип данных. Она не содержит правил по созданию этих данных. Нужно всего лишь вернуть что-то. При работе с объектами, мне нравится работать с шаблоном открытого модуля, но вы можете использовать другой подход, если хотите.
Как упоминал выше, все типы это синглтоны, так что, если мы изменим foo.variable в одном месте, в других местах она тоже изменится.
Service
Сервис (не путайте общее название с конкретным типом) работает так же как фабрика. Разница в том, что сервис использует конструктор, поэтому, когда используете его в первый раз, он выполнит new Foo(); для создания экземпляра объекта. Имейте в виду, что этот же объект вернется и в других местах, если использовать там этот сервис.
Фактически сервис эквивалентен следующему коду:
Foobar является классом, и мы создаем его экземпляр на нашей фабрике, используем его первый раз, а затем возвращаем. Как и сервис, экземпляр класса Foobar будет создан только однажды и в следующий раз фабрика вернет этот же экземпляр снова.
Если у нас уже есть класс, и мы хотим использовать его в нашем сервисе, можем сделать так:
Provider
Провайдер это фабрика, настроенная особым образом. Фактически фабрика из последних примеров будет выглядеть как-то так:
Почему нужно использовать эту форму, когда фабрика гораздо проще? Потому что провайдер можно настроить в конфигурационной функции. Так что можно сделать что-то вроде этого:
Бонус 1: Декоратор
Здесь мы можем делать все что захотим хотим, чтобы декорировать наш сервис. В нашем случае, мы добавили функцию greet в оригинальный сервис. Затем вернули новый модифицированный сервис.
Возможность декорирования сервисов удобна при использовании сервисов от сторонних разработчиков, которые можно декорировать без необходимости копирования в свой проект и дальнейших модификаций.
Примечание: Константу декорировать нельзя.
Бонус 2: создание новых экземпляров
Наши сервисы синглтоны, но мы можем создать синглтон-фабрику, которая создает новые экземпляры. Прежде чем углубиться, имейте в виду, что наличие сервисов-синглтонов хороший подход, который мы не хотим менять. Но в тех редких случаях, когда нужно сгенерировать новые экземпляры, можно сделать это так:
Так что у нас есть функция класса, которая создаст новый объект Person на основе идентификатора, который мы передадим (так будет в реальном коде) и каждый экземпляр сможет обновлять сам себя. Теперь просто нужно создать сервис, который будет его использовать.
Респект Джошу Дэвиду Миллеру за его пример.
Бонус 3: CoffeeScript
CoffeeScript можно удобно сочетать с сервисами, поскольку они обеспечивают более симпатичный способ создания классов. Давайте рассмотрим Бонусный пример 2 с использованием CoffeeScript:
Сейчас он выглядит красивее, по моему скромному мнению.
Сейчас он выглядит ужаснее по скромному мнению переводчика.
Как избежать дублирования экземпляров сервисов в Angular
В этой статье мы рассмотрим, когда и почему Angular создает два экземпляра одного и того же сервиса и какие существуют решения для обеспечения того, чтобы сервис оставался единым целым во всем приложении.
Эта тема неоднократно описывалась в других статьях и документации, но когда я столкнулся с этой проблемой, мне все равно приходилось собирать информацию по крупицам и делать множество тестовых примеров на Stackblitz, чтобы узнать, как это сделать правильно, поэтому я решил написать эту статью в качестве заметки для себя в будущем.
Введение
Почему вы можете этого хотеть?
Наиболее распространенной причиной является разделение некоторого ценного состояния между всеми частями вашего приложения.
Взгляните на простой сервис настройки приложений:
В основном я хочу использовать одни и те же настройки для всего приложения:
А затем использовать его в каком-то компоненте:
Что ж, давайте посмотрим, почему это происходит и как с этим справиться.
Проблема
Angular создаст новые экземпляры для любого из InjectionToken или Injectable в случаях использования:
Это происходит потому, что Angular создает новый модуль Injector для любого лениво загруженного модуля, это поведение отлично описано в документации и в этой статье.
Вот демо с демонстрацией проблемы.
Решения
По сути, вы можете добавить сервис к поставщикам прикладного модуля, и он будет работать. Но другие разработчики могут не знать, что вы хотите использовать этот сервис в качестве одиночного, и кто-то добавит этот сервис в список поставщиков загруженного модуля, и Angular создаст его второй экземпляр.
Вы должны выбрать, какую стратегию использовать, потому что есть два с ее плюсами и минусами:
forRoot()
Метод forRoot является своего рода соглашением между разработчиками Angular для вызова этого метода только в корневом модуле (например AppModule ), поэтому любая служба будет предоставляться только один раз.
Примечание: метод forRoot не обрабатывается никаким кодом в Angular Compiler, поэтому вы можете называть его как хотите ( forMySuperHotRootAppModule() ), но это не рекомендуется.
Хороший пример использования forRoot это RouterModule.
providedIn: ‘root’
Пример с синглтоном
Вы можете легко узнать, создал ли кто-то второй экземпляр вашего сервиса.
Также это можно сделать как базовый класс:
Бонус
Вот несколько вопросов, на которые я искал ответы, когда столкнулся с этой проблемой.
Как справиться с этим с InjectionToken?
У InjectionToken есть второй аргумент.
Что делать, если я использую forRoot в сочетании с providedIn: ‘root’?
Русские Блоги
Исследование проблем синглтона Angular Service
вступление
Когда мы используем Angular Service, у нас есть такой сценарий: состояние переменной является глобально эффективным, и значение состояния может быть изменено в разных компонентах, и каждый компонент может получить переменную состояния в Service. Последнее значение. Для реализации этого сценария одним из наиболее важных моментов является то, что Служба должна быть одноэлементной. Поскольку при наличии нескольких экземпляров значение переменной состояния, видимое каждым Компонентом, несовместимо. В этой статье мы попытаемся изучить, при каких обстоятельствах Angular Service будет генерировать несколько экземпляров и как решить эту проблему.
Официальный документ Angular оСинглтон-сервисПо теме есть хорошее обсуждение. В дополнение к этой статье, эта статья также относится к другим статьям, обсуждающим похожие темы, в сочетании с моим собственным пониманием. Критика и обсуждение приветствуются.
Когда будет несколько экземпляров?
В общем, Angular будет генерировать службу с несколькими экземплярами в двух ситуациях:
Заявить, что общий модуль Сервиса импортируется несколькими другими модулями.
Объявить, что общий модуль Сервиса импортируется другими модулями с отложенной загрузкой.
Эти две ситуации выглядят схожими, но на самом деле принципы, лежащие в их основе, несколько различаются. Давайте рассмотрим их по отдельности.
Вот этот CustomerService Это также может быть необходимо в других модулях приложения, а компоненты других модулей необходимо импортировать перед внедрением. CustomerModule Модуль. Это использование очень распространено в сценариях приложений Angular Router. Когда нам нужно разделить определение маршрутизатора, нам нужно определить разные модули маршрутизации (например, CustomerRoutingModule) и импортировать их в соответствующие модули их соответствующей логики, и эти модули маршрутизации должны импортировать модуль Angular Router. Однако в модуле маршрутизатора есть служба маршрутизатора, которая удовлетворяет первой ситуации, упомянутой выше.Модуль маршрутизатора импортируется несколькими модулями, в результате чего создается несколько экземпляров службы маршрутизатора. Следовательно, Angular сам должен решить эту проблему.
Прежде чем объяснять, как Angular решает эту проблему, давайте рассмотрим детали. Корневой модуль Angular (обычно AppModule) собирается вдоль древовидной структуры модуля во время компиляции, и все поставщики, объявленные модулем по пути, объединяются в один и тот же массив, а для внедрения зависимостей предоставляется инжектор. Другими словами, хотя зависимость модуля Angular является деревом, в конечном итоге она будет объединена в модуль, а структура Injector будет плоской. Если вы хотите узнать об этом больше, вы можете обратиться кЗдесь。
Однако, хотя инжектор такой же, служба по-прежнему является мультиэкземплярной за счет объявления службы в массиве Provider и импорта модуля. В приведенном выше примере при использовании внедрения зависимостей в модуле каждый раз, когда встречается тип CustomerModule Если модуль импортируется, вызывается унифицированный инжектор для создания экземпляра. CustomerService Объект.
Как решить?
Angular предлагает три способа решения этой проблемы:
использовать providedIn Альтернативный синтаксис для регистрации службы в модуле
Реализовать сервис в вызывающем модуле
Определено в модуле forRoot() с forChild() Статический метод
Давайте посмотрим на них по очереди.
использовать providedIn грамматика
Этот синтаксисAngular 6.0После того,Официальный документТакже есть представления. Синтаксис providedIn означает, что эта служба предоставляется в корневом модуле приложения, поэтому нам не нужно объявлять эту службу в массиве предоставления любого модуля. Например:
Реализовать сервис в вызывающем модуле
Это эквивалентно тому, что каждый модуль реализует свою собственную службу, конечно, не будет проблем с несколькими экземплярами. Но когда масштаб программы станет больше, многие Сервисы неизбежно потребуются совместно с модулями, и этот метод не удастся.
Определите статические методы forRoot () и forChild () в модуле
Этот метод может быть вам знаком. Кстати, он используется для регистрации маршрутизации при использовании модуля маршрутизации Angular. Например:
Теперь вы можете понять, почему модуль маршрутизации определяет forRoot Метод: RoutingModule Angular часто упоминается в других модулях, и в этом модуле определены некоторые необходимые службы. Чтобы гарантировать, что эти службы являются одиночными, реализован RoutingModule. forRoot метод.
Так почему forRoot или же forChild Будет ли метод работать? Фактически, эти два имени метода являются только рекомендуемыми идиомами. Метод, который вы определяете в своем собственном модуле, не обязательно может называться этим именем. Если вы соответствуете определенным спецификациям, вы можете реализовать одноэлементную службу для модуля. Посмотрим один forRoot Реализация метода:
Мы видим, что forRoot Статический метод, возвращающийModuleWithProviders Тип интерфейса. Для этого типа требуется один ngModule Поле и один providers Поле массива. Этот метод можно понимать как еще один метод объявления модуля, предоставляемого Angular, через forRoot Этот статический метод получает экземпляр Module.
на forRoot с forChild Разница заключается вПри их внедрении вы в providers Укажите услугу。 forRoot Метод будет уточнен, forChild Нет, поэтому первый может быть вызван только один раз, а второй может быть вызван несколько раз. в случае forRoot Когда метод вызывается несколько раз, все еще возможно сгенерировать службу с несколькими экземплярами.
Конечно, вы также можете предоставить два набора различных сервисов двумя способами.Когда определенная группа должна быть импортирована, соответствующий метод вызывается при импорте модуля.
forRoot Метод используется следующим образом:
Это обеспечит TestModule Сервис, заявленный в синглтоне.
В практических приложениях есть исключение из ситуации, когда модуль загружается лениво через маршрутизацию. Отличительной особенностью модуля с отложенной загрузкой является то, что он имеет собственный инжектор для создания экземпляра службы. Как мы упоминали ранее, Angular объединяет все модули в один модуль во время компиляции, но модули с отложенной загрузкой являются динамическими, и они не будут объединены. Следовательно, если вы ссылаетесь на общий модуль в модуле с отложенной загрузкой, то экземпляр службы действителен только в модуле с отложенной загрузкой, что может привести к созданию службы с несколькими экземплярами.
Поэтому для сценариев с отложенной загрузкой модулей хорошей привычкой является не объявлять Сервис в общем Модуле, а регистрировать Сервис в Модуле более высокого уровня или использовать providedIn грамматика.
Что касается проблемы синглтонов службы, то документы официального веб-сайта Angular были подробно обсуждены.Если вы хотите понять внедрение зависимостей службы и механизм управления синглтонами, вам нужно обратиться к исходному коду.
Angular 6+ полное руководство по внедрению зависимостей. providedIn vs providers:[]
В Angular 6 появился новый улучшенный синтаксис для внедрения зависимостей сервисов в приложение (provideIn). Несмотря на то, что уже вышел Angular 7, эта тема до сих пор остается актуальной. Существует много путаницы в комментариях GitHub, Slack и Stack Overflow, так что давайте подробно разберем эту тему.
В данной статье мы рассмотрим:
Внедрение зависимостей (dependency Injection)
Внедрение зависимостей (DI) — это способ создания объектов, которые зависят от других объектов. Система внедрения зависимостей предоставляет зависимые объекты, когда создает экземпляр класса.
Формальные объяснения хороши, но давайте разберем более подробно, что такое внедрение зависимостей.
Все компоненты и сервисы являются классами. Каждый класс имеет специальный метод constructor, при вызове которого создается объект-экземпляр данного класса, использующийся в приложении.
Допустим в одном из наших сервисов имеется следующий код:
Если создавать его, не используя механизм внедрения зависимостей, то необходимо добавить HttpClient вручную. Тогда код будет выглядеть следующим образом:
Но откуда в таком случае взять httpClient? Его тоже необходимо создать:
Но откуда теперь взять httpHandler? И так далее, пока не будут созданы экземпляры всех необходимых классов. Как мы видим, ручное создание может быть сложным и в процессе могут возникать ошибки.
Механизм внедрения зависимостей Angular делает все это автоматически. Все, что нам нужно сделать, это указать зависимости в конструкторе компонентов, и они будут добавлены без каких-либо усилий с нашей стороны.
Старый способ внедрения зависимостей в Angular (providers: [])
Для запуска приложения Angular должен знать о каждом отдельном объекте, который мы хотим внедрить в компоненты и сервисы. До релиза Angular 6 единственным способом сделать это было указание сервисов в свойстве providers: [] декораторов @NgModule, @Сomponent и @Directive.
Разберем три основных случая использования providers: []:
Модули, загружаемые с приложением (Eager)
В данном случае сервис регистрируется в глобальной области видимости как синглтон. Он будет синглтоном даже если включен в providers[] нескольких модулей. Создается единственный экземпляр класса сервиса, который будет зарегистрирован на уровне корня приложения.
Модули с отложенной загрузкой (Lazy)
Экземпляр сервиса, подключенного к lazy модулю, будет создан во время его инициализации. Добавление такого сервиса в компонент eager модуля приведет к ошибке: No provider for MyService! error.
Внедрение в @Сomponent и @Directive
При внедрении в компонент или директиву создается отдельный экземпляр сервиса, который будет доступен в данном компоненте и всех дочерних. В этой ситуации сервис не будет синглтоном, его экземпляр будет создаваться каждый раз при использовании компонента и удаляться вместе с удалением компонента из DOM.
Новый способ внедрения зависимостей в Angular (providedIn: ‘root’ | SomeModule)
В Angular 6 мы получили новый инструмент “Tree-shakable providers” для внедрения зависимостей в приложение, который можно использовать с помощью свойства providedIn декоратора @Injectable.
Можно представить providedIn как внедрение зависимостей в обратном направлении: раньше в модуле описывались сервисы, в которые он будет подключен, теперь в сервисе определяется модуль, к которому его подключать.
Сервис может быть внедрен в корень приложения(providedIn: ‘root’) или в любой модуль (providedIn: SomeModule). providedIn: ‘root’ является сокращением для внедрения в AppModule.
Разберем основные сценария использования нового синтаксиса:
Внедрение в корневой модуль приложения (providedIn: ‘root’)
Это самый распространенный вариант внедрения зависимостей. В данном случае сервис будет добавлен в бандл приложение только если он реально используется, т.е. внедрен в компонент или другой сервис.
При использовании нового подхода не будет особой разницы в монолитном SPA приложении, где используются все написанные сервисы, однако providedIn: ‘root’ будет полезен при написании библиотек.
Раньше все сервисы библиотеки необходимо было добавить в providers:[] её модуля. После импорта библиотеки в приложение в бандл добавлялись все сервисы, даже если использовался только один. В случае с providedIn: ‘root’ нет необходимости подключать модуль библиотеки. Достаточно просто внедрить сервис в нужный компонент.
Модуль с отложенной загрузкой (lazy) и providedIn: ‘root’
Что произойдет, если внедрить сервис с providedIn: ‘root’ в lazy модуль?
Технически ‘root’ обозначает AppModule, но Angular достаточно умен, чтоб добавить сервис в бандл lazy модуля, если он внедрен только в его компоненты и сервисы. Но есть одна проблема (хотя некоторые люди утверждают, что это фича). Если позже внедрить сервис, используемый только в lazy модуле, в основной модуль, то сервис будет перенесен в основной бандл. В больших приложениях с множеством модулей и сервисов это может привести к проблемам с отслеживанием зависимостей и непредсказуемому поведению.
Будьте внимательны! Внедрение одного сервиса во множестве модулей может привести к скрытым зависимостям, которые сложно понять и невозможно распутать.
К счастью есть способы предотвратить это, и мы рассмотрим их ниже.
Внедрение зависимости в немедленно загружаемый модуль (eager)
Как правило, этот кейс не имеет смысла и вместо него мы можем использовать providedIn: ‘root’. Подключение сервиса в EagerModule может использоваться для инкапсуляции и предотвратит внедрение без подключения модуля, но в большинстве случаев такой необходимости нет.
Если действительно понадобится ограничить область видимости сервиса, проще воспользоваться старым способом providers:[], так как он точно не приведет к циклическим зависимостям.
По возможности старайтесь использовать providedIn: ‘root’ во всех eager модулях.
Примечание. Преимущество модулей с отложенной загрузкой(lazy)
Одной из основных фич Angular является возможность легко разбивать приложение на фрагменты, что дает следующие преимущества:
Еще одним преимуществом изолированности lazy модуля является то, что ошибка, допущенная в нем, не повлияет на остальную часть приложения. Теперь можно спать спокойно даже в день релиза.
Внедрение в модуль с отложенной загрузкой(providedIn: LazyModule)
Внедрение зависимости в определенный модуль не дает использовать сервис в остальных частях приложения. Это позволяет сохранить структуру зависимостей, что особо полезно для больших приложений, в которых беспорядочное внедрение зависимостей может привести к путанице.
Интересный факт: Если lazy сервис внедрить в основную часть приложения, то сборка (даже AOT) пройдет без ошибок, но приложение упадет с ошибкой «No provider for LazyService».
Проблема с циклической зависимостью
Воспроизвести ошибку можно следующим образом:
Решить эту проблему можно, создав подмодуль LazyServiceModule, который будет подключен в LazyModule. К подмодулю подключить сервисы.
В данном случае придется создать дополнительный модуль, но это не потребует много усилий и даст следующие плюсы:
Внедрение сервиса в компонент (providedIn: SomeComponent)
Существует ли возможность внедрить сервис в @Сomponent или @Directive с использованием нового синтаксиса?
На данный момент нет!
Для создания экземпляра сервиса на каждый компонент все так же необходимо использовать providers: [] в декораторах @Сomponent или @Directive.
Рекомендации по использованию нового синтаксиса в приложениях
Библиотеки
providedIn: ‘root’ хорошо подходит для создания библиотек. Это действительно удобный способ подключить в основное приложение только непосредственно используемую часть функционала и уменьшить размер конечной сборки.
Одним из практических примеров является библиотека ngx-model, которая была переписана с использованием нового синтаксиса и теперь называется @angular-extensions/model. В новой реализации нет необходимости подключать NgxModelModule в приложение, достаточно просто внедрить ModelFactory в нужный компонент. Подробности реализации можно посмотреть тут.
Модули с отложенной загрузкой(lazy)
Используйте для сервисов отдельный модуль providedIn: LazyServicesModule и подключайте его в LazyModule. Такой подход инкапсулирует сервисы и не даст подключить их в другие модули. Это обозначит границы и поможет создать масштабируемую архитектуру.
По моему опыту случайное внедрение в основной или дополнительный модуль (с использованием providedIn: ‘root’) может привести к путанице и является не лучшим решением!
providedIn: ‘root’ тоже будет работать корректно, но при использовании providedIn: LazyServideModule мы получим ошибку «missing provider» при внедрении в другие модули и сможем исправить архитектуру. Перенести сервис в более подходящее место в основной части приложения.
В случаях, когда необходимо конфигурировать модуль. Например, подключать сервис только в SomeModule.forRoot(someConfig).
С другой стороны, в такой ситуации можно использовать providedIn: ‘root’. Это даст гарантию того, что сервис будет добавлен в приложение только один раз.