Bagaimana cara membuat layanan tunggal di Angular 2?


142

Saya pernah membaca bahwa menyuntikkan ketika bootstrap seharusnya membuat semua anak berbagi contoh yang sama, tetapi komponen utama dan tajuk saya (aplikasi utama mencakup komponen tajuk dan router-outlet) masing-masing mendapatkan turunan terpisah dari layanan saya.

Saya memiliki FacebookService yang saya gunakan untuk melakukan panggilan ke api javascript facebook dan UserService yang menggunakan FacebookService. Inilah bootstrap saya:

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

Dari pencatatan saya, sepertinya bootstrap call selesai, lalu saya melihat FacebookService kemudian UserService dibuat sebelum kode di setiap konstruktor berjalan, MainAppComponent, HeaderComponent, dan DefaultComponent:

masukkan deskripsi gambar di sini


8
Anda yakin belum menambahkan UserServicedan FacebookServiceke providerstempat lain?
Günter Zöchbauer

Jawaban:


132

Jason sepenuhnya benar! Ini disebabkan oleh cara injeksi ketergantungan bekerja. Ini didasarkan pada injektor hirarkis.

Ada beberapa injektor dalam aplikasi Angular2:

  • Root yang Anda konfigurasikan saat bootstrap aplikasi Anda
  • Injektor per komponen. Jika Anda menggunakan komponen di dalam yang lain. Injektor komponen adalah anak dari komponen induk. Komponen aplikasi (komponen yang Anda tentukan saat meningkatkan aplikasi) memiliki injektor root sebagai komponen induknya).

Ketika Angular2 mencoba menyuntikkan sesuatu ke dalam konstruktor komponen:

  • Itu terlihat pada injektor yang terkait dengan komponen. Jika ada yang cocok, ia akan menggunakannya untuk mendapatkan instance yang sesuai. Instance ini dibuat dengan malas dan merupakan singleton untuk injector ini.
  • Jika tidak ada penyedia pada level ini, ia akan melihat injector induk (dan seterusnya).

Jadi, jika Anda ingin memiliki satu singleton untuk seluruh aplikasi, Anda harus memiliki provider yang ditentukan pada level root injector atau komponen aplikasi injector.

Tapi Angular2 akan melihat pohon injektor dari bawah. Ini berarti bahwa penyedia pada level terendah akan digunakan dan cakupan instance yang terkait adalah level ini.

Lihat pertanyaan ini untuk lebih jelasnya:


1
Terima kasih, itu menjelaskannya dengan baik. Itu hanya kontra-intuitif bagi saya karena agak rusak dengan paradigma komponen mandiri 2 sudut. Jadi katakanlah saya membuat perpustakaan komponen untuk Facebook, tetapi saya ingin mereka semua menggunakan layanan tunggal. Mungkin ada komponen untuk menampilkan gambar profil pengguna yang masuk, dan satu lagi untuk diposkan. Aplikasi yang menggunakan komponen-komponen itu harus memasukkan layanan Facebook sebagai penyedia bahkan jika itu tidak menggunakan layanan itu sendiri? Saya hanya bisa mengembalikan layanan dengan metode 'getInstance ()' yang mengelola layanan tunggal yang sebenarnya ...
Jason Goemaat

@tThierryTemplier Bagaimana saya melakukan yang sebaliknya, saya memiliki kelas layanan umum yang ingin saya masukkan dalam banyak komponen tetapi instantiate instance baru setiap kali (opsi penyedia / arahan seharusnya tidak digunakan lagi dan dihapus pada rilis berikutnya)
Boban Stojanovski

Maaf karena sangat bodoh tetapi tidak jelas bagi saya bagaimana Anda membuat layanan tunggal, dapatkah Anda menjelaskan lebih detail?
gyozo kudor

Jadi, untuk bekerja dengan satu instance layanan, haruskah itu dinyatakan sebagai penyedia di app.module.ts atau di app.component.ts?
user1767316

Mendeklarasikan setiap layanan hanya di app.module.ts, melakukan pekerjaan untuk saya.
user1767316

142

Perbarui (Sudut 6 +)

Cara yang disarankan untuk membuat layanan singleton telah berubah. Sekarang disarankan untuk menentukan di @Injectabledekorator pada layanan yang harus disediakan di 'root'. Ini sangat masuk akal bagi saya dan tidak perlu lagi mendaftar semua layanan yang disediakan dalam modul Anda. Anda hanya mengimpor layanan ketika Anda membutuhkannya dan mereka mendaftarkan diri di tempat yang tepat. Anda juga dapat menentukan modul sehingga hanya akan disediakan jika modul diimpor.

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

Perbarui (Sudut 2)

Dengan NgModule, cara untuk melakukannya sekarang saya pikir adalah membuat 'CoreModule' dengan kelas layanan Anda di dalamnya, dan daftar layanan di penyedia modul. Kemudian Anda mengimpor modul inti dalam modul aplikasi utama Anda yang akan memberikan satu contoh untuk setiap anak yang meminta kelas itu dalam konstruktor mereka:

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

Jawaban Asli

Jika Anda mendaftar penyedia bootstrap(), Anda tidak perlu mendaftarkannya di dekorator komponen Anda:

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

Bahkan mendaftarkan kelas Anda di 'penyedia' menciptakan instance baru, jika ada komponen orang tua yang mencantumkannya maka anak-anak tidak perlu, dan jika mereka melakukannya mereka akan mendapatkan instance baru.


1
Mulai sekarang (Januari 2017), Anda akan mencantumkan layanan di [providers]dalam file modul Anda, bukan di bootstrap(), kan?
Jason Swett

5
Mengapa tidak menempatkan ApiServicedi AppModule's providers, dan dengan demikian menghindari kebutuhan untuk CoreModule? (Tidak yakin inilah yang disarankan @JasonSwett.)
Jan Aagaard

1
@JasonGoemaat Bisakah Anda menambahkan contoh bagaimana Anda menggunakannya dalam komponen? Kita dapat mengimpor ApiServicedi atas tetapi mengapa repot-repot untuk meletakkannya di array penyedia CoreModule dan kemudian mengimpor yang di app.module ... itu belum mengklik untuk saya, belum.
hogan

Jadi, menempatkan layanan pada penyedia modul akan menyediakan singleton untuk modul itu. Dan menempatkan layanan pada penyedia Komponen akan membuat contoh baru untuk setiap instance komponen? Apakah itu benar?
BrunoLM

@ BrunoLM Saya membuat aplikasi uji untuk membantu menunjukkan apa yang terjadi. Menarik bahwa meskipun TestServiceditentukan dalam modul inti dan modul aplikasi, instance tidak dibuat untuk modul karena disediakan oleh komponen sehingga sudut tidak pernah mendapatkan setinggi pohon injektor. Jadi jika Anda menyediakan layanan dalam modul Anda dan tidak pernah menggunakannya, sebuah instance sepertinya tidak akan dibuat.
Jason Goemaat

24

Saya tahu angular memiliki injector hirarkis seperti yang dikatakan Thierry.

Tapi saya punya pilihan lain di sini jika Anda menemukan kasus penggunaan di mana Anda tidak benar-benar ingin menyuntikkannya pada orang tua.

Kita dapat mencapainya dengan membuat turunan dari layanan, dan menyediakan selalu mengembalikannya.

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

Dan kemudian pada komponen Anda, Anda menggunakan metode pemberian kustom.

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

Dan Anda harus memiliki layanan tunggal tanpa bergantung pada injector hirarkis.

Saya tidak mengatakan ini adalah cara yang lebih baik, kalau-kalau ada orang yang memiliki masalah di mana injector hirarkis tidak mungkin.


1
Haruskah SHARE_SERVICE_PROVIDERberada YOUR_SERVICE_PROVIDERdi komponen? Saya juga berasumsi bahwa mengimpor file layanan diperlukan seperti biasa dan konstruktor masih akan memiliki parameter tipe 'Layanan Anda', kan? Saya suka ini saya pikir, memungkinkan Anda untuk menjamin singleton dan tidak harus memastikan layanan disediakan hierarki. Ini juga memungkinkan masing-masing komponen untuk mendapatkan salinan mereka sendiri dengan hanya mendaftarkan layanan di providersbukan penyedia tunggal, kan?
Jason Goemaat

@JasonGoemaat Anda benar. Diedit itu. Tepatnya, Anda melakukannya dengan cara yang persis sama di konstruktor dan pada penyedia komponen yang Anda tambahkan YOUR_SERVICE_PROVIDER. Ya, semua komponen akan mendapatkan instance yang sama dengan hanya menambahkannya di penyedia.
Joel Almeida

Pada titik pengembang menggunakan ini, mereka harus bertanya pada diri sendiri "mengapa saya bahkan menggunakan Angular sama sekali jika saya tidak akan menggunakan mekanisme yang disediakan Angular untuk situasi ini?" Menyediakan layanan di tingkat aplikasi harus memadai untuk setiap situasi dalam konteks Angular.
RyNo

1
+1 Meskipun ini adalah cara untuk membuat layanan tunggal, ia berfungsi dengan sangat baik sebagai cara untuk menciptakan layanan multitonial dengan hanya mengubah instanceproperti menjadi peta contoh nilai-kunci
Precastic

1
@RyNo Saya dapat membayangkan aplikasi yang tidak memerlukan layanan untuk setiap rute, atau modul yang dapat digunakan kembali yang menginginkan instance statis dan ingin menggunakan instance yang sama dengan modul lain yang menggunakannya. Mungkin sesuatu yang membuat koneksi soket web ke server dan memproses pesan. Mungkin hanya beberapa rute di aplikasi yang ingin menggunakannya sehingga tidak perlu membuat instance layanan dan koneksi soket web ketika aplikasi dimulai jika itu tidak diperlukan. Anda dapat memprogram di sekitar itu untuk memiliki komponen 'init' layanan di mana pun ia digunakan, tetapi pada permintaan lajang akan berguna.
Jason Goemaat

16

Sintaks telah diubah. Periksa tautan ini

Dependensi adalah lajang dalam lingkup injektor. Dalam contoh di bawah ini, sebuah instance HeroService tunggal dibagikan di antara para HeroesComponent dan anak-anak HeroListComponentnya.

Langkah 1. Buat kelas tunggal dengan dekorator @Injectable

@Injectable()
export class HeroService {
  getHeroes() { return HEROES;  }
}

Langkah 2. Suntikkan dalam konstruktor

export class HeroListComponent { 
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }

Langkah 3. Daftarkan penyedia

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    routing,
    HttpModule,
    JsonpModule
  ],
  declarations: [
    AppComponent,
    HeroesComponent,
    routedComponents
  ],
  providers: [
    HeroService
  ],
  bootstrap: [
    AppComponent
  ]
})
export class AppModule { }

bagaimana jika Injectablekelas saya bukan layanan dan hanya berisi staticstring untuk penggunaan global?
Syed Ali Taqi

2
seperti penyedia ini: [{sediakan: 'API_URL', useValue: ' coolapi.com '}]
Whisher

7

Menambahkan @Injectabledekorator ke Layanan, DAN mendaftarkannya sebagai penyedia di Modul Root akan menjadikannya singleton.


Katakan saja jika saya memahaminya. Jika saya melakukan apa yang Anda katakan, ok, itu akan menjadi singleton. Jika, selain itu, layanan ini juga merupakan penyedia dalam modul lain, itu tidak akan menjadi singleton, kan? Karena hirarki.
heringer

1
Dan jangan mendaftarkan penyedia di dekorator @Component halaman.
Laura

@Laura. Apakah saya masih mengimpornya ke komponen yang benar-benar menggunakan layanan ini?
Tandai

@ Markus Ya, Anda perlu mengimpornya, dan kemudian Anda hanya perlu mendeklarasikannya dalam constructorseperti ini: import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
Laura

6

ini sepertinya bekerja dengan baik untuk saya

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

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

8
Saya akan menyebutnya anti-pola Angular2. Berikan layanan dengan benar dan Angular2 akan selalu menyuntikkan instance yang sama. Lihat juga stackoverflow.com/questions/12755539/...
Günter Zöchbauer

3
@ Günter Zöchbauer, bisa tolong beri saran tentang "Berikan layanan dengan benar dan Angular2 akan selalu menyuntikkan contoh yang sama." ? Karena tidak jelas dan saya tidak dapat menemukan informasi yang berguna dengan googling.
nowiko

Saya baru saja memposting jawaban ini yang mungkin membantu dengan pertanyaan Anda stackoverflow.com/a/38781447/217408 (lihat juga tautannya di sana)
Günter Zöchbauer

2
Ini sempurna. Anda harus menggunakan injeksi dependensi angulars sendiri, tetapi tidak ada salahnya menggunakan pola ini untuk benar-benar yakin bahwa layanan Anda adalah singleton ketika Anda mengharapkannya. Berpotensi menghemat banyak waktu mencari bug hanya karena Anda menyuntikkan layanan yang sama di dua tempat berbeda.
PaulMolloy

Saya menggunakan pola ini untuk memastikan bahwa masalah yang saya hadapi adalah karena layanan tidak singleton
hr-tis

5

Berikut adalah contoh kerja dengan Angular versi 2.3. Panggil saja konstruktor dari layanan dengan cara stand seperti konstruktor ini (private _userService: UserService). Dan itu akan membuat singleton untuk aplikasi.

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 serviceadalah layanan yang hanya memiliki satu instance di aplikasi.

Ada (2) cara untuk menyediakan layanan tunggal untuk aplikasi Anda.

  1. menggunakan providedInproperti, atau

  2. menyediakan modul langsung di AppModuleaplikasi

Menggunakan disediakan dalam

Dimulai dengan Angular 6.0, cara yang lebih disukai untuk membuat layanan tunggal adalah dengan mengatur providedInuntuk root pada @Injectable()dekorator layanan. Ini memberitahu Angular untuk menyediakan layanan di root aplikasi.

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

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

Array penyedia NgModule

Dalam aplikasi yang dibangun dengan versi Angular sebelum 6.0, layanan terdaftar array penyedia NgModule sebagai berikut:

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

Jika ini NgModuleadalah root AppModule, UserService akan menjadi singleton dan tersedia di seluruh aplikasi. Meskipun Anda mungkin melihatnya dikodekan dengan cara ini, menggunakan providedInproperti @Injectable()dekorator pada layanan itu sendiri lebih disukai pada Angular 6.0 karena membuat layanan Anda mudah diguncang.


3

Anda dapat menggunakan useValuepenyedia

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

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

5
useValuetidak terkait dengan singleton. Usevalue hanya untuk melewatkan nilai alih-alih Type( useClass) yang memanggil DI newatau useFactoryketika fungsi dilewatkan yang mengembalikan nilai ketika dipanggil oleh DI. Angular DI mempertahankan satu instance per penyedia secara otomatis. Hanya berikan sekali saja dan Anda memiliki singleton. Maaf, saya harus mengundurkan diri karena itu hanya informasi yang tidak valid: - /
Günter Zöchbauer

3

Dari Angular @ 6, Anda dapat memiliki providedIndalam Injectable.

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

}

Periksa dokumen di sini

Ada dua cara untuk membuat layanan tunggal di Angular:

  1. Menyatakan bahwa layanan harus disediakan di root aplikasi.
  2. Sertakan layanan di AppModule atau dalam modul yang hanya diimpor oleh AppModule.

Dimulai dengan Angular 6.0, cara yang lebih disukai untuk membuat layanan tunggal adalah dengan menentukan pada layanan yang harus disediakan di root aplikasi. Ini dilakukan dengan menyetel yang disediakan ke root pada dekorator @Injectable layanan:


Ini bagus, tetapi Anda mungkin juga memiliki masalah tak terduga dengan variabel yang tidak ada di sana yang mungkin diselesaikan dengan mendeklarasikan beberapa item public static.
cjbarth

2

Cukup nyatakan layanan Anda sebagai penyedia di app.module.ts saja.

Itu berhasil bagi saya.

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

lalu instanciate dengan menggunakan parameter pribadi konstruktor:

constructor(private topicService: TopicService) { }

atau karena jika layanan Anda digunakan dari html, opsi -prod akan mengklaim:

Property 'topicService' is private and only accessible within class 'SomeComponent'.

tambahkan anggota untuk layanan Anda dan isi dengan instance yang diterima di konstruktor:

export class SomeComponent {
  topicService: TopicService;
  constructor(private topicService: TopicService) { 
    this.topicService= topicService;
  }
}

0
  1. Jika Anda ingin membuat layanan singleton pada level aplikasi, Anda harus mendefinisikannya app.module.ts

    penyedia: [MyApplicationService] (Anda dapat menentukan modul anak yang sama juga untuk membuatnya spesifik modul)

    • Jangan menambahkan layanan ini di penyedia yang membuat contoh untuk komponen yang melanggar konsep singleton, cukup menyuntikkan melalui konstruktor.
  2. Jika Anda ingin mendefinisikan layanan singleton pada tingkat komponen, buat layanan, tambahkan layanan itu di app.module.ts dan tambahkan array penyedia di dalam komponen tertentu seperti yang ditunjukkan dalam snipet di bawah ini.

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

  3. Angular 6 menyediakan cara baru untuk menambahkan layanan di tingkat aplikasi. Alih-alih menambahkan kelas layanan ke array penyedia [] di AppModule, Anda dapat mengatur konfigurasi berikut di @Injectable ():

    @Injectable ({providedIn: 'root'}) kelas ekspor MyService {...}

"Sintaks baru" memang menawarkan satu keuntungan: Layanan dapat dimuat dengan malas oleh Angular (di belakang layar) dan kode redundan dapat dihapus secara otomatis. Ini dapat menghasilkan kinerja dan kecepatan pemuatan yang lebih baik - meskipun ini sebenarnya hanya cocok untuk layanan dan aplikasi yang lebih besar secara umum.


0

Selain jawaban yang sangat bagus di atas, mungkin ada hal lain yang hilang jika hal-hal di singleton Anda masih belum berlaku sebagai singleton. Saya mengalami masalah ketika memanggil fungsi publik pada singleton dan menemukan bahwa itu menggunakan variabel yang salah. Ternyata masalahnya thisadalah tidak dijamin terikat dengan singleton untuk setiap fungsi publik di singleton. Ini dapat diperbaiki dengan mengikuti saran di sini , seperti:

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

Atau, Anda dapat dengan mudah mendeklarasikan anggota kelas sebagai public staticganti public, maka konteksnya tidak masalah, tetapi Anda harus mengaksesnya seperti SubscriptableService.onServiceRequested$bukannya menggunakan injeksi ketergantungan dan mengaksesnya melalui this.subscriptableService.onServiceRequested$.


0

Layanan orangtua dan anak

Saya mengalami masalah dengan layanan orang tua dan anaknya menggunakan contoh yang berbeda. Untuk memaksa satu instance digunakan, Anda dapat alias induk dengan referensi ke anak di penyedia modul aplikasi Anda. Orang tua tidak akan dapat mengakses properti anak, tetapi contoh yang sama akan digunakan untuk kedua layanan. 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}
]

Layanan yang digunakan oleh komponen di luar ruang lingkup modul aplikasi Anda

Saat membuat perpustakaan yang terdiri dari komponen dan layanan, saya mengalami masalah di mana dua contoh akan dibuat. Satu oleh proyek Angular saya dan satu oleh komponen di dalam perpustakaan saya. Cara mengatasinya:

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>

Apakah Anda bermaksud menjawab pertanyaan Anda sendiri? Jika demikian, Anda dapat memisahkan jawaban menjadi Jawaban formal pada StackOverflow, dengan memotong / menempelkannya ke bidang Jawab setelah Pertanyaan diposting.
ryanwebjackson
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.