Как создать одноэлементный сервис в Angular 2?

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

У меня есть сервис FacebookService, который я использую для совершения звонков в API javascript facebook и сервис UserService, который использует сервис FacebookService. Вот мой бутстрап:

bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);

Из моей регистрации это выглядит как завершается вызов начальной загрузки, затем я вижу FacebookService, затем создается UserService до запуска кода в каждом из конструкторов, MainAppComponent, HeaderComponent и DefaultComponent:

введите описание изображения здесь

16 ответов

Решение

Джейсон совершенно прав! Это вызвано тем, как работает внедрение зависимостей. Он основан на иерархических инжекторах.

В приложении 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) {}
}

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

Я знаю, что у 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]
})

И у вас должен быть одиночный сервис, не зависящий от иерархических инжекторов.

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

Синтаксис был изменен. Проверьте эту ссылку

Зависимости являются синглетонами в области действия инжектора. В приведенном ниже примере один экземпляр 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()
export class MyStaticService {
  static instance: MyStaticService;

  constructor() {
    return MyStaticService.instance = MyStaticService.instance || this;
  }
}

Добавление @Injectable Декоратор для Сервиса, И регистрация его в качестве поставщика в Root Module сделает его единственным.

Вот рабочий пример с 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");
        });
    }
    ...
}

А singleton service - это служба, для которой в приложении существует только один экземпляр.

Есть (2) способа предоставить одноэлементную службу для вашего приложения.

  1. использовать providedIn собственность, или

  2. предоставить модуль прямо в 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({
  ...
  providers: [UserService],
  ...
})

Если это NgModule были корнем AppModule, UserService будет одноэлементным и доступен во всем приложении. Хотя вы можете видеть, что это закодировано таким образом, используяprovidedIn собственность @Injectable() Decorator на самом сервисе предпочтительнее, начиная с Angular 6.0, так как он делает ваши сервисы древовидными.

Ты можешь использовать useValue в провайдерах

import { MyService } from './my.service';

@NgModule({
...
  providers: [ { provide: MyService, useValue: new MyService() } ],
...
})

С Angular@6 вы можете иметь providedIn в Injectable,

@Injectable({
  providedIn: 'root'
})
export class UserService {

}

Проверьте документы здесь

Есть два способа сделать сервис одноязычным в Angular:

  1. Объявите, что сервис должен быть предоставлен в корне приложения.
  2. Включите сервис в AppModule или в модуль, который импортируется только AppModule.

Начиная с Angular 6.0, предпочтительным способом создания одноэлементных сервисов является указание для сервиса, что он должен быть предоставлен в корне приложения. Это можно сделать, установив предоставляемый параметр root в корне в декораторе @Injectable службы:

Просто объявите свой сервис в качестве поставщика только в app.module.ts.

Это сделало работу за меня.

providers: [Topic1Service,Topic2Service,...,TopicNService],

затем либо создайте его экземпляр, используя закрытый параметр конструктора:

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;
  }
}

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

ТЛ;ДР

Когда вы предоставляете сервис на корневом уровне, Angular создает единственный общий экземпляр HeroService и внедряет его в любой класс, который его запрашивает. Регистрация провайдера в метаданных @Injectable() также позволяет Angular оптимизировать приложение, удаляя службу из скомпилированного приложения, если она не используется. Этот процесс известен как встряхивание дерева.


3 способа зарегистрировать провайдера он же сервис

Вы должны зарегистрировать хотя бы одного поставщика любой услуги, которой собираетесь пользоваться. Поставщик может быть частью собственных метаданных службы, что делает эту услугу доступной повсюду, или вы можете зарегистрировать поставщиков с определенными модулями или компонентами. Вы регистрируете провайдеров в метаданных сервиса (в декораторе @Injectable()) или в метаданных @NgModule() или @Component().

1 корневой инжектор

По умолчанию команда Angular CLI nggenerate service регистрирует поставщика в корневом инжекторе вашего сервиса, включая метаданные поставщика в декоратор @Injectable(). В учебнике этот метод используется для регистрации поставщика определения класса HeroService.

      @Injectable({
 providedIn: 'root',
})

Когда вы предоставляете сервис на корневом уровне, Angular создает единственный общий экземпляр HeroService и внедряет его в любой класс, который его запрашивает. Регистрация провайдера в метаданных @Injectable() также позволяет Angular оптимизировать приложение, удаляя службу из скомпилированного приложения, если она не используется. Этот процесс известен как встряхивание дерева.

2 Уровень модуля

Когда вы регистрируете провайдера с определенным NgModule, один и тот же экземпляр службы доступен всем компонентам в этом NgModule. Чтобы зарегистрироваться на этом уровне, используйте свойствопровайдеров декоратора @NgModule().

      @NgModule({
  providers: [
  BackendService,
  Logger
 ],
 …
})

3 уровня компонентов

Когда вы регистрируете поставщика на уровне компонента, вы получаете новый экземпляр службы с каждым новым экземпляром этого компонента. На уровне компонента зарегистрируйте поставщика услуг в свойстве поставщиков метаданных @Component().

      @Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})

Услуги для родителей и детей

У меня возникли проблемы с родительской службой и ее дочерним элементом, использующим разные экземпляры. Чтобы принудительно использовать один экземпляр, вы можете псевдоним родительского элемента со ссылкой на дочерний элемент в поставщиках модулей приложения. Родитель не сможет получить доступ к свойствам дочернего элемента, но один и тот же экземпляр будет использоваться для обеих служб. https://angular.io/guide/dependency-injection-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>

В дополнение к вышеупомянутым отличным ответам, может быть что-то еще, чего не хватает, если вещи в вашем синглтоне все еще не ведут себя как синглтон. Я столкнулся с проблемой при вызове публичной функции для синглтона и обнаружении, что она использует неправильные переменные. Оказывается, проблема заключалась в 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$,

Что ж, объем службы Angular зависит от того, где вы предоставляете службу в корневом модуле, ленивом загружаемом модуле или на уровне компонентов.

Вот видео, которое прекрасно описывает это с реальными примерами.

https://youtu.be/aDyqnQrer3o

  1. Если вы хотите сделать сервис-синглтон на уровне приложения, вы должны определить его в app.module.ts

    поставщики: [ MyApplicationService ] (вы можете определить то же самое в дочернем модуле, чтобы сделать его специфичным для этого модуля)

    • Не добавляйте эту услугу в провайдера, который создает экземпляр для этого компонента, который нарушает концепцию синглтона, просто внедрите его через конструктор.
  2. Если вы хотите определить одноэлементную службу на уровне компонента, создайте службу, добавьте эту службу в app.module.ts и добавьте в массив провайдеров внутри конкретного компонента, как показано в фрагменте ниже.

    @Component ({селектор: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], поставщики: [TestMyService]})

  3. Angular 6 предоставляет новый способ добавления сервиса на уровне приложения. Вместо того, чтобы добавлять класс обслуживания к массиву provider [] в AppModule, вы можете установить следующую конфигурацию в @Injectable ():

    @Injectable ({обеспечить In: 'root'}) класс экспорта MyService { ... }

"Новый синтаксис" действительно предлагает одно преимущество: сервисы могут быть загружены Angular (скрытно), а избыточный код может быть удален автоматически. Это может привести к повышению производительности и скорости загрузки - хотя в целом это действительно важно для более крупных сервисов и приложений.

Другие вопросы по тегам