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

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

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

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

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

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

Ответ 1

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

В приложении Angular2 есть несколько инжекторов:

  • Корневой, который вы настраиваете при загрузке приложения
  • Инжектор на компонент. Если вы используете компонент внутри другого. Инжектор компонентов - это дочерний компонент родительского компонента. Компонент приложения (тот, который вы указываете при расширении вашего приложения) имеет корневой инжектор в качестве родительского).

Когда Angular2 пытается вставить что-то в конструктор компонента:

  • Он смотрит на инжектор, связанный с компонентом. Если есть соответствующий, он будет использовать его для получения соответствующего экземпляра. Этот экземпляр лениво создан и является одиночным для этого инжектора.
  • Если на этом уровне нет провайдера, он будет смотреть на родительский инжектор (и т.д.).

Итак, если вы хотите иметь одноэлемент для всего приложения, вам необходимо определить провайдера либо на уровне инжектора корня, либо в инжекторе приложения.

Но Angular2 будет смотреть на дерево инжектора снизу. Это означает, что провайдер на самом низком уровне будет использоваться, и объем связанного экземпляра будет таким уровнем.

См. этот вопрос для получения дополнительной информации:

Ответ 2

Обновление (Угловое 6)

Изменен рекомендуемый способ создания службы singleton. В настоящее время рекомендуется указать в @Injectable decorator на службе, что он должен быть указан в "root". Это имеет для меня большой смысл, и нет необходимости перечислять все предоставляемые услуги в ваших модулях. Вы просто импортируете сервисы, когда они вам нужны, и они регистрируются в нужном месте. Вы также можете указать модуль, чтобы он предоставлялся только в том случае, если модуль импортирован.

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

Обновление (Угловое 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) {}
}

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

Ответ 3

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

И вы должны иметь однопользовательскую службу, не зависимо от иерархических инжекторов.

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

Ответ 4

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

Зависимости - это одиночные числа в пределах объема инжектора. В приведенном ниже примере один экземпляр HeroService используется совместно с HeroesComponent и его дочерними элементами HeroListComponent.

Шаг 1. Создайте одноэлементный класс с @Injectable decorator

@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 { }

Ответ 5

Добавление @Injectable decorator в Сервис, И, зарегистрировав его как поставщика в корневом модуле, сделает его одиночным.

Ответ 6

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

Ответ 7

это, кажется, хорошо работает для меня

@Injectable()
export class MyStaticService {
  static instance: MyStaticService;

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

Ответ 8

Вы можете использовать useValue у поставщиков

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

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

Ответ 9

Из Angular @6 вы можете providedIn Injectable.

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

}

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

Существует два способа сделать услугу одноэлементным в Angular:

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

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

Ответ 10

Просто объявляйте свой сервис как поставщика только в 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;
  }
}

Ответ 11

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

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

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

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

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

    @Injectable ({providedIn: 'root'}) класс экспорта MyService {...}

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

Ответ 12

В дополнение к вышеприведенным отличным ответам может быть что-то еще, что отсутствует, если вещи в вашем singleton все еще не ведут себя как одиночный. Я столкнулся с проблемой при вызове публичной функции на синглете и обнаружил, что использует неправильные переменные. Оказывается, проблема заключалась в том, что this не гарантируется привязкой к синглтону для любых публичных функций в singleton. Это можно исправить, следуя советам здесь, например:

@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$.