web-dev-qa-db-ja.com

Angular 2でシングルトンサービスを作成する方法を教えてください。

ブートストラップですべての子が同じインスタンスを共有する必要がある場合のインジェクションを読みましたが、私のメインコンポーネントとヘッダーコンポーネント(メインアプリケーションにはヘッダーコンポーネントとルーターアウトレットが含まれます)はそれぞれ別々のサービスインスタンスを取得します。

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

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

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

enter image description here

126
Jason Goemaat

ジェイソンは完全に正しいです!これは依存性注入が機能する方法によって引き起こされます。それは階層的な注射器に基づいています。

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

  • アプリケーションをブートストラップするときに設定するルートのもの
  • コンポーネントごとのインジェクタ。あなたが他のものの中のコンポーネントを使うならば。コンポーネントインジェクタは、親コンポーネントのインジェクタの子です。アプリケーションコンポーネント(アプリケーションをブーストするときに指定するもの)は、ルートインジェクタを親コンポーネントとして持ちます。

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

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

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

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

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

120

アップデート(Angular 6)

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

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

アップデート(Angular 2)

NgModuleを使って、今それを行う方法は、その中にあなたのサービスクラスを持つ 'CoreModule'を作成し、そしてモジュールのプロバイダーにサービスをリストすることであると思います。次に、メインのappモジュールにコアモジュールをインポートして、コンストラクタでそのクラスを要求しているすべての子に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) {}
}

実際、あなたのクラスを 'プロバイダ'にリストアップすることはそれの新しいインスタンスを作成します、もしいずれかの親コンポーネントがすでにそれをリストアップしていれば子は必要としません。

128
Jason Goemaat

Thierryが言ったように、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]
})

そして、階層型インジェクタに頼らずにシングルトンサービスを受けるべきです。

これがより良い方法ではない、階層型インジェクタが使用できないという問題がある場合に備えて

24
Joel Almeida

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

依存関係はインジェクタの範囲内のシングルトンです。以下の例では、単一の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 { }
16
Manish Jain

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

7
bresleveloper

これは私にとってうまくいっているようです

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

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

これは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");
        });
    }
    ...
}
5
David Dehghan

Angular @ 6以降、providedInInjectableを含めることができます。

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

}

ここで ドキュメントをチェックしてください

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

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

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

3
sabithpocker

プロバイダでuseValueを使うことができます

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

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

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;
  }
}
2
user1767316
  1. アプリケーションレベルでサービスシングルトンを作成したい場合は、app.module.tsで定義する必要があります。

    プロバイダー:[MyApplicationService](子モジュールでも同じように定義して、そのモジュール固有のものにすることができます)

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

    @Component({selector: 'app-root'、templateUrl: './test.component.html'、styleUrls:['./test.component.scss']、プロバイダー:[TestMyService]})

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

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

「新しい構文」には1つの利点があります。サービスはAngular(舞台裏で)遅延的にロードでき、冗長なコードは自動的に削除できます。これは、パフォーマンスと読み込み速度の向上につながります。ただし、これは実際にはより大規模なサービスやアプリ全般に対してのみ有効です。

0
Vijay Barot

上記の優れた答えに加えて、あなたのシングルトンの中のものがシングルトンとして振舞っていないのなら、何か他のものが欠けているかもしれません。シングルトンでパブリック関数を呼び出し、それが間違った変数を使用していたことを発見したとき、私は問題に遭遇しました。問題は、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ではなくpublic staticとして宣言すれば、コンテキストは関係ありませんが、依存性注入を使用してSubscriptableService.onServiceRequested$を介してアクセスする代わりにthis.subscriptableService.onServiceRequested$のようにアクセスする必要があります。

0
cjbarth