Я читал, что инъекция при начальной загрузке должна иметь все дочерние элементы, использующие один и тот же экземпляр, но каждый из моих основных компонентов и компонентов заголовка (основное приложение включает компонент заголовка и маршрутизатор) получает отдельный экземпляр моих служб.
У меня есть FacebookService, который я использую для звонков в facebook javascript api и UserService, который использует FacebookService. Вот мой бутстрап:
bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);
Судя по моему журналу, похоже, что вызов начальной загрузки завершается, затем я вижу, что FacebookService, затем UserService создается до того, как код в каждом из конструкторов запускается, MainAppComponent, HeaderComponent и DefaultComponent:
angular
typescript
angular2-routing
angular2-services
Джейсон Гоэмаат
источник
источник
UserService
и ниFacebookService
кproviders
чему?Ответы:
Джейсон совершенно прав! Это вызвано тем, как работает внедрение зависимостей. Он основан на иерархических инжекторах.
В приложении Angular2 есть несколько инжекторов:
Когда Angular2 пытается что-то внедрить в конструктор компонента:
Итак, если вы хотите иметь синглтон для всего приложения, вам необходимо определить поставщика либо на уровне корневого инжектора, либо на уровне инжектора компонентов приложения.
Но Angular2 будет смотреть на дерево инжекторов снизу. Это означает, что будет использоваться поставщик на самом низком уровне, и область действия связанного экземпляра будет на этом уровне.
См. Этот вопрос для получения более подробной информации:
источник
Обновление (Angular 6+)
Изменился рекомендуемый способ создания одноэлементной службы . Теперь рекомендуется указать в
@Injectable
декораторе службы, что она должна находиться в «корне». Для меня это имеет большой смысл, и больше нет необходимости перечислять все предоставляемые услуги в ваших модулях. Вы просто импортируете услуги, когда они вам нужны, и они регистрируются в нужном месте. Вы также можете указать модуль, чтобы он предоставлялся только в том случае, если модуль импортирован.@Injectable({ providedIn: 'root', }) export class ApiService { }
Обновление (Angular 2)
Я думаю, что с помощью NgModule способ сделать это сейчас - создать «CoreModule» с вашим классом обслуживания в нем и перечислить службу в поставщиках модуля. Затем вы импортируете основной модуль в свой основной модуль приложения, который предоставит один экземпляр всем дочерним элементам, запрашивающим этот класс в своих конструкторах:
CoreModule.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ApiService } from './api.service'; @NgModule({ imports: [ CommonModule ], exports: [ // components that we want to make available ], declarations: [ // components for use in THIS module ], providers: [ // singleton services ApiService, ] }) export class CoreModule { }
AppModule.ts
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AppComponent } from './app.component'; import { CoreModule } from './core/core.module'; @NgModule({ declarations: [ AppComponent ], imports: [ CommonModule, CoreModule // will provide ApiService ], providers: [], bootstrap: [ AppComponent ] }) export class AppModule { }
Оригинальный ответ
Если вы укажете поставщика в
bootstrap()
, вам не нужно указывать его в декораторе компонентов:import { ApiService } from '../core/api-service'; @Component({ selector: 'main-app', templateUrl: '/views/main-app.html', // DO NOT LIST PROVIDERS HERE IF THEY ARE IN bootstrap()! // (unless you want a new instance) //providers: [ApiService] }) export class MainAppComponent { constructor(private api: ApiService) {} }
Фактически, перечисление вашего класса в «поставщики» создает новый его экземпляр, если какой-либо родительский компонент уже перечисляет его, тогда дочерним элементам это не нужно, и если они это сделают, они получат новый экземпляр.
источник
[providers]
в файле модуля, а не в немbootstrap()
, верно?ApiService
вAppModule
«сproviders
, и таким образом избежать необходимостиCoreModule
? (Не уверен , что это то , что предполагает @JasonSwett.)ApiService
вверху, но зачем вообще вообще нужно помещать его в массив поставщиков CoreModule, а затем импортировать его в app.module ... для меня это еще не сработало.TestService
это и указано в модулях ядра и приложения, экземпляры не создаются для модулей, потому что они предоставляются компонентом, поэтому angular никогда не поднимается так высоко в дереве инжекторов. Поэтому, если вы предоставляете услугу в своем модуле и никогда не используете ее, экземпляр не создается.Я знаю, что у angular есть иерархические форсунки, как сказал Тьерри.
Но у меня есть другой вариант на тот случай, если вы найдете вариант использования, когда вы действительно не хотите вводить его в родительский элемент.
Мы можем добиться этого, создав экземпляр службы и всегда возвращая его при условии предоставления.
import { provide, Injectable } from '@angular/core'; import { Http } from '@angular/core'; //Dummy example of dependencies @Injectable() export class YourService { private static instance: YourService = null; // Return the instance of the service public static getInstance(http: Http): YourService { if (YourService.instance === null) { YourService.instance = new YourService(http); } return YourService.instance; } constructor(private http: Http) {} } export const YOUR_SERVICE_PROVIDER = [ provide(YourService, { deps: [Http], useFactory: (http: Http): YourService => { return YourService.getInstance(http); } }) ];
А затем в своем компоненте вы используете свой собственный метод предоставления.
@Component({ providers: [YOUR_SERVICE_PROVIDER] })
И у вас должен быть одноэлементный сервис, не зависящий от иерархических инжекторов.
Я не говорю, что это лучший способ, на тот случай, если у кого-то возникнет проблема, когда иерархические форсунки невозможны.
источник
SHARE_SERVICE_PROVIDER
бытьYOUR_SERVICE_PROVIDER
в компоненте? Также я предполагаю, что импорт служебного файла необходим как обычно, а в конструкторе все еще будет параметр типа YourService, верно? Мне это нравится, я думаю, это позволяет вам гарантировать одноэлементность и не нужно убедиться, что услуга предоставляется вверх по иерархии. Это также позволяет отдельным компонентам получать свою собственную копию, просто перечисляя службуproviders
вместо поставщика синглтонов, верно?YOUR_SERVICE_PROVIDER
. Да, все компоненты получат один и тот же экземпляр, просто добавив его в провайдеры.instance
свойство в карту экземпляров ключ-значениеСинтаксис был изменен. Проверить эту ссылку
Зависимости являются одиночными в рамках инжектора. В приведенном ниже примере один экземпляр HeroService используется совместно HeroesComponent и его дочерними компонентами HeroListComponent.
Шаг 1. Создайте одноэлементный класс с декоратором @Injectable
@Injectable() export class HeroService { getHeroes() { return HEROES; } }
Шаг 2. Ввести в конструктор
export class HeroListComponent { constructor(heroService: HeroService) { this.heroes = heroService.getHeroes(); }
Шаг 3. Зарегистрируйте провайдера
@NgModule({ imports: [ BrowserModule, FormsModule, routing, HttpModule, JsonpModule ], declarations: [ AppComponent, HeroesComponent, routedComponents ], providers: [ HeroService ], bootstrap: [ AppComponent ] }) export class AppModule { }
источник
Injectable
класс не является службой и просто содержитstatic
строки для глобального использования?Добавление
@Injectable
декоратора к Сервису И регистрация его в качестве поставщика в корневом модуле сделает его синглтоном.источник
constructor
следующим образом:import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
мне кажется, это хорошо работает
@Injectable() export class MyStaticService { static instance: MyStaticService; constructor() { return MyStaticService.instance = MyStaticService.instance || this; } }
источник
Вот рабочий пример с Angular версии 2.3. Просто вызовите конструктор службы, как этот конструктор (private _userService: UserService). И он создаст синглтон для приложения.
user.service.ts
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Rx'; import { Subject } from 'rxjs/Subject'; import { User } from '../object/user'; @Injectable() export class UserService { private userChangedSource; public observableEvents; loggedUser:User; constructor() { this.userChangedSource = new Subject<any>(); this.observableEvents = this.userChangedSource.asObservable(); } userLoggedIn(user:User) { this.loggedUser = user; this.userChangedSource.next(user); } ... }
app.component.ts
import { Component } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { UserService } from '../service/user.service'; import { User } from '../object/user'; @Component({ selector: 'myApp', templateUrl: './app.component.html' }) export class AppComponent implements OnInit { loggedUser:User; constructor(private _userService:UserService) { this._userService.observableEvents.subscribe(user => { this.loggedUser = user; console.log("event triggered"); }); } ... }
источник
Вы можете использовать
useValue
в провайдерахimport { MyService } from './my.service'; @NgModule({ ... providers: [ { provide: MyService, useValue: new MyService() } ], ... })
источник
useValue
не имеет отношения к синглтону. Usevalue - это просто передать значение вместоType
(useClass
), которое вызывает DInew
илиuseFactory
где передается функция, которая возвращает значение при вызове DI. Angular DI автоматически поддерживает один экземпляр для каждого поставщика. Только предоставьте его один раз, и у вас будет синглтон. Извините, я должен проголосовать против, потому что это просто неверная информация: - /Из Angular @ 6 вы можете иметь
providedIn
файлInjectable
.@Injectable({ providedIn: 'root' }) export class UserService { }
Проверьте документы здесь
источник
public static
.Просто объявите свою услугу поставщиком только в app.module.ts.
Это сработало для меня.
затем либо создайте его, используя закрытый параметр конструктора:
constructor(private topicService: TopicService) { }
или поскольку, если ваша служба используется из html, опция -prod потребует:
Property 'topicService' is private and only accessible within class 'SomeComponent'.
добавьте участника для своей службы и заполните его экземпляром, полученным в конструкторе:
export class SomeComponent { topicService: TopicService; constructor(private topicService: TopicService) { this.topicService= topicService; } }
источник
А
singleton service
- это служба, для которой в приложении существует только один экземпляр.Есть (2) способа предоставить одноэлементную службу для вашего приложения.
использовать
providedIn
свойство, илипредоставить модуль прямо в
AppModule
приложенииИспользование providedIn
Начиная с Angular 6.0, предпочтительным способом создания одноэлементной службы является установка
providedIn
root в@Injectable()
декораторе службы . Это говорит Angular предоставить службу в корне приложения.import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class UserService { }
Массив поставщиков NgModule
В приложениях, созданных с использованием версий Angular до 6.0, службы зарегистрированы как массивы поставщиков NgModule следующим образом:
Если бы это
NgModule
был кореньAppModule
, UserService был бы одноэлементным и доступным во всем приложении. Хотя вы можете видеть, что это закодировано таким образом, использованиеprovidedIn
свойства@Injectable()
декоратора в самой службе предпочтительнее, начиная с Angular 6.0, поскольку это делает ваши службы древовидными.источник
Если вы хотите сделать одноэлементный сервис на уровне приложения, вы должны определить его в app.module.ts
провайдеры: [MyApplicationService] (вы можете определить то же самое в дочернем модуле, чтобы сделать его специфичным для этого модуля)
Если вы хотите определить одноэлементную службу на уровне компонента, добавьте эту службу в app.module.ts и добавьте массив поставщиков внутри определенного компонента, как показано в фрагменте ниже.
@Component ({селектор: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], провайдеры: [TestMyService]})
Angular 6 предоставляет новый способ добавления сервиса на уровне приложения. Вместо того, чтобы добавлять класс обслуживания в массив Provider [] в AppModule, вы можете установить следующую конфигурацию в @Injectable ():
@Injectable ({providedIn: 'root'}) класс экспорта MyService {...}
«Новый синтаксис» имеет одно преимущество: Angular может лениво загружать сервисы (за кулисами), а избыточный код можно автоматически удалять. Это может привести к повышению производительности и скорости загрузки - хотя на самом деле это работает только для более крупных сервисов и приложений в целом.
источник
В дополнение к приведенным выше отличным ответам может быть что-то еще, чего не хватает, если что-то в вашем синглтоне по-прежнему не ведет себя как синглтон. Я столкнулся с проблемой при вызове публичной функции на синглтоне и обнаружил, что она использует неправильные переменные. Оказывается, проблема заключалась в
this
том, что не гарантируется привязка к синглтону для каких-либо общедоступных функций в синглтоне. Это можно исправить, следуя приведенным здесь советам , например:@Injectable({ providedIn: 'root', }) export class SubscriptableService { public serviceRequested: Subject<ServiceArgs>; public onServiceRequested$: Observable<ServiceArgs>; constructor() { this.serviceRequested = new Subject<ServiceArgs>(); this.onServiceRequested$ = this.serviceRequested.asObservable(); // save context so the singleton pattern is respected this.requestService = this.requestService.bind(this); } public requestService(arg: ServiceArgs) { this.serviceRequested.next(arg); } }
В качестве альтернативы вы можете просто объявить члены класса как
public static
вместоpublic
, тогда контекст не будет иметь значения, но вам придется обращаться к ним, какSubscriptableService.onServiceRequested$
вместо использования внедрения зависимостей и доступа к ним черезthis.subscriptableService.onServiceRequested$
.источник
Услуги для родителей и детей
У меня возникли проблемы с родительской службой и ее дочерней службой, использующей разные экземпляры. Чтобы принудительно использовать один экземпляр, вы можете установить псевдоним родительского элемента со ссылкой на дочерний элемент в поставщиках модулей приложения. Родитель не сможет получить доступ к свойствам дочернего элемента, но один и тот же экземпляр будет использоваться для обеих служб. https://angular.io/guide/dependency-injection-providers#aliased-class-providers
app.module.ts
providers: [ ChildService, // Alias ParentService w/ reference to ChildService { provide: ParentService, useExisting: ChildService} ]
Сервисы, используемые компонентами за пределами области модулей вашего приложения
При создании библиотеки, состоящей из компонента и службы, я столкнулся с проблемой, когда будут созданы два экземпляра. Один в моем проекте Angular и один в компоненте моей библиотеки. Исправление:
my-outside.component.ts
@Component({...}) export class MyOutsideComponent { @Input() serviceInstance: MyOutsideService; ... }
my-inside.component.ts
constructor(public myService: MyOutsideService) { }
my-inside.component.hmtl
<app-my-outside [serviceInstance]="myService"></app-my-outside>
источник