Як створити одиночну службу в Angular 2?


142

Я читав, що впорскування під час завантаження повинна мати всіх дітей, що мають один і той же екземпляр, але основні компоненти та заголовки (основне додаток включає заголовок компонента та маршрутизатор) отримують окремий екземпляр моїх служб.

У мене є FacebookService, який я використовую для здійснення дзвінків у facebook javascript api та UserService, який використовує FacebookService. Ось мій завантажувач:

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

З мого журналу схоже на те, що виклик завантажувальної програми закінчується, тоді я бачу, що FacebookService, а потім UserService створюється до запуску коду в кожному з конструкторів, MainAppComponent, HeaderComponent і DefaultComponent:

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


8
Ви впевнені , що Ви НЕ додали UserServiceі FacebookServiceв providersіншому місці?
Günter Zöchbauer

Відповіді:


132

Джейсон абсолютно прав! Це викликано тим, як працює ін'єкція залежності. Він заснований на ієрархічних форсунках.

У програмі Angular2 є кілька інжекторів:

  • Кореневий, який ви налаштовуєте під час завантаження програми
  • Інжектор на компонент. Якщо ви використовуєте компонент всередині іншого. Компонентний інжектор є дочірнім батьківським компонентом. Компонент програми (той, який ви вказали під час завантаження програми), має головний інжектор як батьківський).

Коли Angular2 намагається ввести щось у конструктор компонентів:

  • Він заглядає в інжектор, пов'язаний з компонентом. Якщо є одна відповідна, вона використовуватиме її для отримання відповідного екземпляра. Цей екземпляр ліниво створений і є сингтом для цього інжектора.
  • Якщо на цьому рівні немає постачальника, він перегляне батьківський інжектор (тощо).

Отже, якщо ви хочете мати синглтон для всієї програми, вам потрібно визначити постачальника або на рівні кореневого інжектора, або інжектора компонента програми.

Але Angular2 дивиться на дерево інжектора знизу. Це означає, що постачальник на найнижчому рівні буде використовуватися, а сфера пов'язаного екземпляра буде цього рівня.

Дивіться це питання для більш детальної інформації:


1
Дякую, що це добре пояснює Для мене це було просто контр-інтуїтивно, тому що це своєрідне розрив із самодостатньою компонентною парадигмою 2. Тож скажімо, я роблю бібліотеку компонентів для Facebook, але я хочу, щоб усі вони використовували послугу одиночки. Можливо, є компонент для відображення профілю профілю зареєстрованого користувача, а ще один - для публікації. Додаток, який використовує ці компоненти, повинен включати послугу Facebook в якості постачальника, навіть якщо він не використовує саму послугу? Я міг би просто повернути послугу методом 'getInstance ()', який керує
синглом

@tThierryTemplier Як би я робив навпаки, у мене є загальний клас обслуговування, який я хочу вводити в декілька компонентів, але інстанціювати кожен раз новий екземпляр (параметри постачальників / директив повинні бути застарілими та видалені в наступному випуску)
Бобан Стояновський,

Вибачте за те, що ви такі дурні, але мені незрозуміло, як ви створюєте одиночну службу, можете пояснити більш докладно?
gyozo kudor

Отже, для роботи з одним екземпляром послуги, чи слід її оголошувати провайдером у app.module.ts або в app.component.ts?
користувач1767316

Декларуючи кожну службу лише в app.module.ts, зробив роботу для мене.
користувач1767316

142

Оновлення (кутова 6 +)

Рекомендований спосіб створення одиночної послуги змінився. Тепер рекомендується в @Injectableдекораторі вказати на сервісі, що він повинен надаватися у "корені". Це має для мене багато сенсу, і більше не потрібно перераховувати всі надані послуги у ваших модулях. Ви просто імпортуєте сервіси, коли вони вам потрібні, і вони зареєструються в потрібному місці. Ви також можете вказати модуль, щоб він був наданий лише у випадку імпорту модуля.

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

Фактично перерахування вашого класу на «провайдерів» створює його новий екземпляр, якщо якийсь батьківський компонент вже перелічив його, то дітям цього не потрібно, і якщо вони будуть, вони отримають новий екземпляр.


1
Відтепер (січень 2017 року) ви б перерахували службу [providers]у файлі свого модуля, а не в цьому bootstrap(), правда?
Джейсон Світт

5
Чому б не поставити ApiServiceв AppModule«з providers, і таким чином уникнути необхідності CoreModule? (Не впевнений, що це пропонує @JasonSwett.)
Ян Аагаард,

1
@JasonGoemaat Чи можете ви додати приклад того, як ви використовуєте його в компоненті? Ми можемо імпортувати ApiServiceверхню частину, але чому тоді взагалі не турбуємося помістити його в масив постачальників CoreModule, а потім імпортувати той у app.module ... він ще не натискав на мене.
hogan

Таким чином, розміщення послуги постачальника модулів забезпечить одиночний модуль. І розміщення служби постачальникам компонентів створить новий примірник для кожного примірника компонента? Це так?
BrunoLM

@BrunoLM Я створив тестовий додаток, щоб допомогти показати, що відбувається. Цікаво, що, незважаючи на те TestService, що визначено як в базових, так і в додаткових модулях, для модулів не створюються екземпляри, оскільки він надається компонентом, так що кутова ніколи не отримує так високе дерево інжектора. Тож якщо ви надаєте послугу у своєму модулі і ніколи не використовуєте її, схоже, екземпляр не створюється.
Джейсон Гімаат

24

Я знаю, що кутові є ієрархічні форсунки, як сказав Тьєррі.

Але в мене є ще один варіант, якщо ви знайдете футляр, коли ви не хочете вводити його у батьків.

Ми можемо досягти цього, створивши екземпляр служби, і при наданні завжди повертаємо це.

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]
})

І ви повинні мати одиночне обслуговування, не залежно від ієрархічних форсунок.

Я не кажу, що це кращий спосіб - це лише на випадок, коли у когось є проблеми, коли ієрархічні інжектори неможливі.


1
Чи повинен SHARE_SERVICE_PROVIDERбути YOUR_SERVICE_PROVIDERкомпонент? Також я припускаю, що імпорт файлу служби потрібен як звичайний, і у конструктора все ще буде параметр типу "YourService", правда? Мені подобається це, я думаю, дозволяє вам гарантувати одиноку, і не потрібно переконатися, що послуга надається за ієрархією. Це також дозволяє окремим компонентам отримати свою власну копію, просто перерахувавши службу providersзамість постачальника синглів, правда?
Джейсон Гімаат

@JasonGoemaat ви праві. Редагував це. Саме так, ви робите це точно так само, як у конструкторі та постачальниках цього компонента, який ви додаєте YOUR_SERVICE_PROVIDER. Так, всі компоненти отримають один і той же екземпляр, просто додавши його до провайдерів.
Джоель Альмейда

Після того, як розробник використовує це, вони повинні запитати себе: "чому я взагалі використовую Angular, якщо я не збираюся використовувати механізми, передбачені Angular для цієї ситуації?" Надання послуги на рівні заявок повинно бути достатнім для кожної ситуації в контексті Angular.
RyNo

1
+1 Хоча це спосіб створення одиночних служб, він дуже непогано служить способом створення мультитонової служби, просто змінивши instanceвластивість на карту екземплярів ключових значень
Precastic,

1
@RyNo Я можу уявити додаток, який не потребує обслуговування для кожного маршруту, або модуль багаторазового використання, який хоче статичний екземпляр і хоче використовувати той самий екземпляр з будь-якими іншими модулями, які його використовують. Можливо, щось, що створює з'єднання веб-сокета з сервером і обробляє повідомлення. Можливо, лише кілька маршрутів у додатку захотіли б використовувати його, тому не потрібно створювати примірник сервісу та підключення до веб-розетки, коли програма запускається, якщо це не потрібно. Ви можете запрограмувати це, щоб компоненти "ініціювали" службу скрізь, де вона використовується, але одиночні на вимогу були б корисні.
Джейсон Джеймз

16

Синтаксис змінено. Перевірте це посилання

Залежності є одинаковими в межах інжектора. У наведеному нижче прикладі один екземпляр 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рядки для глобального використання?
Сид Алі Такі

2
як у цього постачальника: [{provide: 'API_URL', useValue: ' coolapi.com '}]
Whisher

7

Додавання @Injectableдекоратора до Сервісу та реєстрація його як постачальника у Root Module зробить його однократним.


Просто скажіть, чи я це розумію. Якщо я зроблю те, що ви сказали, добре, це буде сингл. Якщо, окрім цього, послуга є також постачальником в іншому модулі, це вже не буде сингтом, правда? Через ієрархію.
heringer

1
І не реєструйте постачальника в оформленні сторінок @Component.
Лора

@Laura. Чи все ж імпортую його в компоненти, які фактично використовують послугу?
Марк

@ Марк Так, вам потрібно імпортувати його, і тоді вам потрібно буде лише оголосити constructorтак: import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
Лаура

6

це, здається, добре працює для мене

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

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

8
Я б назвав це анти-візерунком Angular2. Надайте сервіс правильно, і Angular2 завжди буде вводити один і той же екземпляр. Дивіться також stackoverflow.com/questions/12755539 / ...
Гюнтер Zöchbauer

3
@ Günter Zöchbauer, будь ласка, дайте поради щодо "Надайте послугу правильно, і Angular2 завжди вводить один і той же екземпляр". ? Тому що це незрозуміло, і я не зміг знайти жодної корисної інформації за допомогою Google.
nowiko

Я щойно опублікував цю відповідь, яка може допомогти у вашому питанні stackoverflow.com/a/38781447/217408 (див. Також посилання там)
Günter Zöchbauer

2
Це прекрасно. Вам слід використовувати власні ін'єкції залежності від вугла, але немає жодної шкоди для використання цього шаблону, щоб бути абсолютно впевненим, що ваша послуга є одинаковою, коли ви очікуєте її. Потенційно економиться багато часу на полюванні на помилок лише тому, що ви надаєте одну і ту ж службу в двох різних місцях.
PaulMolloy

Я використав цю схему, щоб встановити, що проблема, з якою я стикався, сталася через те, що служба не була одиночною
hr-tis

5

Ось робочий приклад з кутовою версією 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");
        });
    }
    ...
}

4

A singleton service- це послуга, для якої у додатку існує лише один екземпляр.

Існують (2) способи надання послуги одиночки для вашої програми.

  1. використовувати providedInмайно, або

  2. надайте модуль безпосередньо в AppModuleдодатку

Використання передбаченихIn

Починаючи з Angular 6.0, кращим способом створення одиночної служби є встановлення providedInкорінця на @Injectable()декораторі служби. Це повідомляє Angular надавати послугу в корені програми.

import { Injectable } from '@angular/core';

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

Масив постачальників NgModule

У додатках, побудованих з кутовими версіями до 6.0, сервіси реєструються масивами постачальників NgModule таким чином:

@NgModule({
  ...
  providers: [UserService],
  ...
})

Якби це NgModuleкорінь AppModule, UserService був би однократним і доступним у всьому додатку. Хоча ви можете бачити, що це закодовано таким чином, використання providedInвластивості @Injectable()декоратора в самій сервісі є кращим, ніж у Angular 6.0, оскільки це робить ваші послуги непохитними.


3

Ви можете використовувати useValueв провайдерах

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

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

5
useValueне відноситься до одинакових. Usevalue - це просто передати значення замість Type( useClass), яке викликає DI, newабо useFactoryде передано функцію, яка повертає значення при виклику DI. Angular DI автоматично підтримує один екземпляр на одного постачальника. Надайте лише один раз, і у вас є одиночний. Вибачте, я маю право подати заявку, оскільки це просто недійсна інформація: - /
Günter Zöchbauer

3

З кутового @ 6, ви можете мати providedInв Injectable.

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

}

Перевірте документи тут

Є два способи зробити послугу одинарною в кутовій:

  1. Заявіть, що послугу слід надавати у корені програми.
  2. Включіть послугу в AppModule або в модуль, який імпортується тільки AppModule.

Починаючи з Angular 6.0, кращим способом створення одиночних служб є вказівка ​​на сервісі, яку вона повинна надавати в корені програми. Це робиться шляхом встановлення наданого корінця на корінь на @Injectable декоратор служби:


Це добре, але у вас також можуть виникнути несподівані проблеми із відсутністю змінних, які можуть бути вирішені шляхом декларування деяких елементів public static.
cjbarth

2

Просто оголосьте про свою послугу як постачальника лише в 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;
  }
}

0
  1. Якщо ви хочете зробити сервіс одиночним на рівні програми, слід визначити його у app.module.ts

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

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

    @Component ({selector: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], постачальники: [TestMyService]})

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

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

"Новий синтаксис", однак, пропонує одну перевагу: Служби можна ліниво завантажувати кутовими (за кадром), а зайвий код можна видалити автоматично. Це може призвести до кращої продуктивності та швидкості завантаження - хоча це дійсно лише для великих служб та додатків загалом.


0

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


0

Послуги для батьків та дітей

У мене виникли проблеми з батьківською службою та її дитиною, використовуючи різні примірники. Щоб примусити використовувати один екземпляр, ви можете встановити псевдонім з посиланням на дитину у постачальників модулів додатків. Батько не зможе отримати доступ до властивостей дитини, але однаковий екземпляр буде використаний для обох служб. 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}
]

Послуги, що використовуються компонентами, що не належать до ваших модулів додатків

Створюючи бібліотеку, що складається з компонента та служби, я зіткнувся з проблемою, де було б створено два екземпляри. Один за моїм кутовим проектом та один за компонентом всередині моєї бібліотеки. Виправлення:

my-external.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>

Ви мали на увазі відповісти на власне запитання? Якщо так, ви можете відокремити відповідь у формальній відповіді на StackOverflow, вирізавши / вставивши її у поле відповіді після розміщення запитання.
ryanwebjackson
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.