Angular 2でシングルトンサービスを作成するにはどうすればよいですか?


142

ブートストラップ時に注入するとすべての子が同じインスタンスを共有するはずであると読みましたが、メインコンポーネントとヘッダーコンポーネント(メインアプリにはヘッダーコンポーネントとルーターアウトレットが含まれています)はそれぞれサービスの個別のインスタンスを取得しています。

私はFacebookのJavaScript APIを呼び出すために使用するFacebookServiceと、FacebookServiceを使用するUserServiceを持っています。これが私のブートストラップです:

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

ログから、ブートストラップ呼び出しが終了したように見えます。その後、各コンストラクター、MainAppComponent、HeaderComponent、およびDefaultComponentのコードが実行される前に、FacebookServiceが作成され、次にUserServiceが作成されます。

ここに画像の説明を入力してください


8
あなたは、あなたがしているならない追加UserService及びFacebookServiceproviders他のどこ?
ギュンターZöchbauer

回答:


132

ジェイソンは完全に正しいです!これは、依存性注入が機能する方法が原因です。それは階層的なインジェクターに基づいています。

Angular2アプリケーションにはいくつかのインジェクターがあります:

  • アプリケーションのブートストラップ時に構成するルート
  • コンポーネントごとのインジェクター。別のコンポーネントの内部でコンポーネントを使用する場合。コンポーネントインジェクタは、親コンポーネント1の子です。アプリケーションコンポーネント(アプリケーションをブーストラップするときに指定するコンポーネント)には、ルートインジェクターが親コンポーネントとして含まれています)。

Angular2がコンポーネントコンストラクタに何かを注入しようとすると:

  • コンポーネントに関連付けられているインジェクタを調べます。一致するものがある場合、それを使用して対応するインスタンスを取得します。このインスタンスは遅延して作成され、このインジェクターのシングルトンです。
  • このレベルにプロバイダーがない場合は、親インジェクター(など)を調べます。

したがって、アプリケーション全体でシングルトンを使用する場合は、ルートインジェクターまたはアプリケーションコンポーネントインジェクターのいずれかのレベルでプロバイダーを定義する必要があります。

しかし、Angular2はインジェクターツリーを下から見ていきます。つまり、最低レベルのプロバイダーが使用され、関連付けられたインスタンスのスコープはこのレベルになります。

詳細については、この質問を参照してください:


1
ありがとう、それはそれをうまく説明しています。これは、angular 2の自己完結型コンポーネントパラダイムで壊れるので、私には直感に反しています。たとえば、Facebookのコンポーネントのライブラリを作成しているとしましょう。ただし、すべてのコンポーネントにシングルトンサービスを使用してもらいたいと考えています。ログインしたユーザーのプロフィール写真を表示するコンポーネントと、投稿するコンポーネントがあるかもしれません。これらのコンポーネントを使用するアプリは、サービス自体を使用していなくても、プロバイダーとしてFacebookサービスを含める必要がありますか?実際のサービスのシングルトンを管理する「getInstance()」メソッドを使用してサービスを返すだけでよい...
Jason Goemaat

@tThierryTemplier反対の方法はありますか?複数のコンポーネントに注入したい共通のサービスクラスがありますが、毎回新しいインスタンスをインスタンス化します(プロバイダー/ディレクティブオプションは次のリリースで非推奨になり削除される予定です)
Boban Stojanovski

馬鹿げて申し訳ありませんが、シングルトンサービスをどのように作成するのか私にはわかりません。詳しく説明できますか?
gyozo kudor 2017

したがって、サービスの単一のインスタンスを操作するには、app.module.tsまたはapp.component.tsでプロバイダーとして宣言する必要がありますか?
user1767316 2018年

app.module.tsでのみ各サービスを宣言し、私の​​ために仕事をしました。
user1767316 2018年

142

更新(Angular 6 +)

シングルトンサービスを作成するための推奨方法が変更されました。@Injectableサービスのデコレータで「ルート」に提供するように指定することをお勧めします。これは私にとって非常に理にかなっており、モジュールで提供されているすべてのサービスを一覧表示する必要はもうありません。必要なときにサービスをインポートするだけで、適切な場所に登録されます。モジュールを指定して、モジュールがインポートされた場合にのみ提供されるようにすることもできます

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

更新(Angular 2)

NgModuleで今それを行う方法は、サービスクラスを含む「CoreModule」を作成し、モジュールのプロバイダーにサービスをリストすることだと思います。次に、コアモジュールをメインアプリモジュールにインポートします。これにより、コンストラクターでそのクラスを要求するすべての子に1つのインスタンスが提供されます。

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年1月)の時点で、サービスをにで[providers]はなく、モジュールファイルにリストしますよねbootstrap()
Jason Swett、2017年

5
なぜ入れないApiServiceAppModuleproviders、ひいてはの必要性を回避CoreModule?(これが@JasonSwettの提案する内容かどうかはわかりません。)
Jan Aagaard 2017年

1
@JasonGoemaatコンポーネントでの使用例を追加できますか?ApiService上部をインポートすることもできますが、なぜそれを実行してCoreModuleのプロバイダー配列に配置し、それをapp.moduleにインポートするのですか?まだクリックできませんでした。
ホーガン2017年

したがって、サービスをモジュールプロバイダーに配置すると、そのモジュールにシングルトンが提供されます。コンポーネントプロバイダーにサービスを配置すると、コンポーネントのインスタンスごとに新しいインスタンスが作成されますか?そうですか?
BrunoLM 2018

@BrunoLM私は何が起こっているかを示すのに役立つテストアプリを作成しました。興味深いのTestServiceは、コアモジュールとアプリモジュールの両方で指定されているにもかかわらず、インスタンスがモジュール用に作成されていないことです。これは、インスタンスがコンポーネントによって提供されるため、angularがインジェクターツリーの上位に到達することがないためです。したがって、モジュールでサービスを提供し、それを使用しない場合、インスタンスは作成されていないようです。
Jason Goemaat

24

ティエリーが言ったように、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]
})

また、階層型インジェクターに依存することなく、シングルトンサービスが必要です。

これがより良い方法だと言っているのではなく、誰かが階層的なインジェクターが不可能な問題を抱えている場合に備えています。


1
コンポーネントに含める必要がSHARE_SERVICE_PROVIDERありYOUR_SERVICE_PROVIDERますか?また、サービスファイルのインポートが通常のように必要であり、コンストラクターにタイプ 'YourService'のパラメーターがまだあると思いますよね?私はこれが好きだと思います。シングルトンを保証することができ、サービスが階層を超えて提供されていることを確認する必要がありません。またproviders、シングルトンプロバイダーの代わりにサービスを一覧表示するだけで、個々のコンポーネントが独自のコピーを取得できるようになります。
Jason Goemaat 2016年

@JasonGoemaatあなたは正しいです。それを編集しました。正確には、コンストラクターで、および追加するコンポーネントのプロバイダーでまったく同じ方法で行いますYOUR_SERVICE_PROVIDER。はい、すべてのコンポーネントは、プロバイダーに追加するだけで同じインスタンスを取得します。
Joel Almeida、2016年

開発者がこれを使用する時点で、「Angularがこの状況に提供するメカニズムを使用しないのに、なぜAngularを使用しているのか」と自問する必要があります。Angularのコンテキスト内のすべての状況で、アプリケーションレベルでサービスを提供することで十分です。
RyNo 2016

1
+1これはシングルトンサービスを作成する方法ですが、プロパティをインスタンスのキー値マップに変更するだけでマルチトンサービスを作成する方法として非常にうまく機能しinstanceます
Precastic

1
@RyNoすべてのルートにサービスを必要としないアプリ、または静的インスタンスを必要とし、それを使用する他のモジュールで同じインスタンスを使用したい再利用可能なモジュールを想像できます。サーバーへのWebソケット接続を作成し、メッセージを処理するものかもしれません。アプリ内のいくつかのルートだけがそれを使用する可能性があるため、不要な場合は、アプリの起動時にサービスインスタンスとWebソケット接続を作成する必要はありません。コンポーネントを使用するすべての場所でサービスを「初期化」するようにプログラムできますが、オンデマンドのシングルトンが便利です。
Jason Goemaat 2016年

16

構文が変更されました。このリンクをチェック

依存関係は、インジェクターの範囲内のシングルトンです。以下の例では、HeroesComponentとそのHeroListComponentの子の間で単一のHeroServiceインスタンスが共有されています。

ステップ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グローバルに使用する文字列のみが含まれている場合はどうなりますか?
Syed Ali Taqi

2
このプロバイダーのように:[{provide: 'API_URL'、useValue: ' coolapi.com '}]
Whisher

7

追加@Injectableサービスにデコレータを、ANDルートモジュール内のプロバイダとしてそれを登録すると、そのシングルトンようになります。


理解できたかどうか教えてください。私があなたの言ったことをやったら、わかりました、それはシングルトンになります。それ以外に、サービスが別のモジュールのプロバイダーでもある場合、シングルトンではなくなりますよね?階層があるからです。
heringer

1
また、ページの@Componentデコレーターにプロバイダーを登録しないでください。
ローラ

@ローラ。実際にサービスを使用するコンポーネントにインポートすることはできますか?
マーク

@マークはい、あなたはそれをインポートする必要があり、その後、あなただけでそれを宣言する必要があり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ünterZöchbauerは、「サービスを正しく提供すると、Angular2は常に同じインスタンスを挿入する」という点についてアドバイスを提供できます。?それは不明確であり、グーグルで有用な情報を見つけることができなかったからです。
nowiko

私はちょうどあなたの質問を持つかもしれないのヘルプというこの回答を投稿stackoverflow.com/a/38781447/217408(もそこにリンクを参照してください)
ギュンターZöchbauer

2
これは完璧です。独自の依存性注入使用する必要がありますが、このパターンを使用して、サービスがシングルトンであると予想される場合に、それがシングルトンであることを完全に確認しても害はありません。同じサービスを2つの異なる場所に挿入しただけで、バグを探す時間を大幅に節約できる可能性があります。
PaulMolloy 2016

私はこのパターンを使用して、直面している問題がシングルトンではないサービスによるものであることを確認しました
hr-tis

5

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");
        });
    }
    ...
}

4

A singleton serviceは、アプリにインスタンスが1つだけ存在するサービスです。

アプリケーションにシングルトンサービスを提供する方法(2)あります。

  1. providedInプロパティを使用する、または

  2. AppModuleアプリケーションのモジュールを直接提供する

ProvidedInの使用

Angular 6.0以降、シングルトンサービスを作成するための推奨される方法はprovidedIn、サービスの@Injectable()デコレーターでrootに設定することです。これにより、アプリケーションルートでサービスを提供するようAngularに指示します。

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

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

NgModuleプロバイダー配列

6.0より前のAngularバージョンでビルドされたアプリでは、サービスは次のようにNgModuleプロバイダー配列に登録されます。

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

これNgModuleがrootのAppModule場合、UserServiceはシングルトンになり、アプリ全体で利用できます。このようにコード化されたように見えるかもしれませんが、Angular 6.0では、サービス自体でデコレータのprovidedInプロパティを使用する@Injectable()ことをお勧めします。


3

useValueプロバイダーで使用できます

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

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

5
useValueシングルトンとは関係ありません。Usevalueは、DIが呼び出すTypeuseClass)ではなく、newまたはDIから呼び出されたuseFactoryときに値を返す関数が渡される場所に値を渡すだけです。Angular DIはプロバイダーごとに単一のインスタンスを自動的に維持します。一度だけ提供すれば、シングルトンになります。申し訳ありませんが、私はそれだけで無効な情報だからdownvoteする必要があります: - /
ギュンターZöchbauer

3

アンギュラ@ 6からは、あなたが持っている可能providedInInjectable

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

}

ここドキュメントを確認してください

Angularでサービスをシングルトンにする方法は2つあります:

  1. サービスはアプリケーションルートで提供する必要があることを宣言します。
  2. AppModuleまたはAppModuleによってのみインポートされるモジュールにサービスを含めます。

Angular 6.0以降では、シングルトンサービスを作成するための推奨される方法は、サービスでアプリケーションルートに提供する必要があることを指定することです。これは、サービスの@Injectableデコレータで、providedInをrootに設定することで行われます。


これは良いことですが、いくつかの項目を宣言することによって解決される可能性のある変数が存在しないという予期しない問題が発生する場合もあります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](子モジュールでも同じを定義して、そのモジュール固有にすることができます)

    • シングルトンの概念を破るコンポーネントのインスタンスを作成するプロバイダーにこのサービスを追加しないでください。コンストラクターを介して注入するだけです。
  2. コンポーネントレベルの作成サービスでシングルトンサービスを定義する場合は、そのサービスをapp.module.tsに追加し、以下のスニペットに示すように、特定のコンポーネント内のプロバイダー配列を追加します。

    @Component({selector: 'app-root'、templateUrl: './test.component.html'、styleUrls:['./test.component.scss']、providers:[TestMyService]})

  3. Angular 6はアプリケーションレベルでサービスを追加する新しい方法を提供します。AppModuleのプロバイダ[]配列にサービスクラスを追加する代わりに、@ Injectable()で次の構成を設定できます。

    @Injectable({providedIn: 'root'})エクスポートクラスMyService {...}

ただし、「新しい構文」には1つの利点があります。Angularがサービスを遅延して(バックグラウンドで)ロードでき、冗長なコードを自動的に削除できます。これにより、パフォーマンスと読み込み速度が向上する可能性がありますが、これは実際には、より大きなサービスとアプリに対してのみ有効です。


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

親子サービス

親サービスとその子が異なるインスタンスを使用していて問題が発生していました。1つのインスタンスを強制的に使用するには、アプリモジュールプロバイダーの子を参照して親にエイリアスを設定できます。親は子のプロパティにアクセスできませんが、同じインスタンスが両方のサービスに使用されます。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}
]

アプリモジュールのスコープ外のコンポーネントによって使用されるサービス

コンポーネントとサービスで構成されるライブラリを作成すると、2つのインスタンスが作成されるという問題が発生しました。1つはAngularプロジェクトによるもので、もう1つはライブラリ内のコンポーネントによるものです。修正:

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>

あなた自身の質問に答えるつもりでしたか?その場合は、質問が投稿された後に回答を[回答]フィールドにカット/ペーストすることで、StackOverflowで回答を正式な回答に分離できます。
ryanwebjackson
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.