簡體   English   中英

如何在 Angular 2 中創建單例服務?

[英]How do I create a singleton service in Angular 2?

我讀過在引導時注入應該讓所有孩子共享同一個實例,但是我的主要和頭組件(主應用程序包括頭組件和路由器出口)每個都獲得了我的服務的單獨實例。

我有一個 FacebookService,我用它來調用 facebook javascript api 和一個使用 FacebookService 的 UserService。 這是我的引導程序:

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

從我的日志記錄來看,引導程序調用完成,然后我看到 FacebookService 然后 UserService 在每個構造函數中的代碼運行之前創建,MainAppComponent、HeaderComponent 和 DefaultComponent:

在此處輸入圖片說明

更新(Angular 6 +)

創建單例服務的推薦方式已更改。 現在建議在服務的@Injectable裝飾器中指定它應該在“根”中提供。 這對我來說很有意義,而且根本不需要在模塊中列出所有提供的服務。 您只需在需要時導入服務,它們就會在適當的位置自行注冊。 您還可以指定一個模塊,以便僅在導入該模塊時才提供該模塊。

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

更新(角度 2)

使用 NgModule,我認為現在的方法是創建一個包含您的服務類的“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) {}
}

事實上,在 'providers' 中列出你的類會創建它的一個新實例,如果任何父組件已經列出它,那么子組件不需要,如果他們這樣做,他們將獲得一個新實例。

傑森完全正確! 這是由依賴注入的工作方式引起的。 它基於分層注入器。

Angular2 應用程序中有幾個注入器:

  • 引導應用程序時配置的根目錄
  • 每個組件一個注射器。 如果您在另一個組件中使用一個組件。 組件注入器是父組件的一個子組件。 應用程序組件(您在 boostrap 應用程序時指定的那個)將根注入器作為父注入器)。

當 Angular2 嘗試在組件構造函數中注入一些東西時:

  • 它查看與組件關聯的注入器。 如果有匹配的,它將使用它來獲取相應的實例。 此實例是延遲創建的,並且是此注入器的單例。
  • 如果此級別沒有提供程序,它將查看父注入器(等等)。

因此,如果您想為整個應用程序創建一個單例,則需要在根注入器或應用程序組件注入器級別定義提供程序。

但是 Angular2 會從底部查看注入器樹。 這意味着將使用最低級別的提供程序,並且關聯實例的范圍將是此級別。

有關更多詳細信息,請參閱此問題:

我知道 angular 有像 Thierry 所說的分層注入器。

但是,如果您發現一個用例,您真的不想將它注入到父級,我在這里還有另一個選擇。

我們可以通過創建服務的實例來實現這一點,並在提供時始終返回該實例。

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

並且您應該擁有一個不依賴分層注入器的單例服務。

我並不是說這是一種更好的方法,只是以防萬一有人遇到無法使用分層注入器的問題。

語法已更改。 檢查此鏈接

依賴項是注入器范圍內的單例。 在下面的示例中,單個 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()
export class MyStaticService {
  static instance: MyStaticService;

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

添加@Injectable裝飾到服務,登記其作為根模塊提供商將使一個單身。

這是一個使用 Angular 2.3 版的工作示例。 只需像這個構造函數(private _userService:UserService) 那樣以stand 方式調用服務的構造函數。 它將為應用程序創建一個單例。

用戶服務.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");
        });
    }
    ...
}

您可以在提供者中使用useValue

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

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

從 Angular@6 開始,您可以在Injectable使用providedIn

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

}

檢查這里文檔

在 Angular 中有兩種方法可以使服務成為單例:

  1. 聲明應該在應用程序根中提供服務。
  2. 將服務包含在 AppModule 或僅由 AppModule 導入的模塊中。

從 Angular 6.0 開始,創建單例服務的首選方法是在服務上指定它應該在應用程序根中提供。 這是通過將 providedIn 設置為服務的 @Injectable 裝飾器上的 root 來完成的:

只需在 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;
  }
}

singleton service是一種應用程序中僅存在一個實例的服務。

(2) 種方法可以為您的應用程序提供單例服務。

  1. 使用providedIn屬性,或

  2. 直接在應用的AppModule中提供模塊

使用提供的輸入

從 Angular 6.0 開始,創建單例服務的首選方法是在服務的@Injectable()裝飾器@Injectable() providedIn為 root。 這告訴 Angular 在應用程序根中提供服務。

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

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

NgModule 提供者數組

在使用 Angular 6.0 之前版本構建的應用程序中,服務注冊 NgModule 提供程序數組如下:

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

如果此NgModule是根AppModule ,則 UserService 將是一個單例並在整個應用程序中可用。 盡管您可能會看到它是這樣編碼的, @Injectable() Angular 6.0 開始,在服務本身上使用@Injectable()裝飾器的providedIn屬性是更可取的,因為它使您的服務可以搖樹。

  1. 如果你想在應用程序級別制作服務單例,你應該在app.module.ts 中定義它

    提供者:[ MyApplicationService ](您也可以在子模塊中定義相同的內容以使其特定於該模塊)

    • 不要在為那個打破單例概念的組件創建實例的 provider 中添加這個服務,只需通過構造函數注入。
  2. 如果您想在組件級別定義單例服務創建服務,請在 app.module.ts 中添加該服務,並在特定組件內添加 providers 數組,如下面的代碼片段所示。

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

  3. Angular 6 提供了在應用程序級別添加服務的新方法。 您可以在 @Injectable() 中設置以下配置,而不是向 AppModule 中的 providers[] 數組添加服務類:

    @Injectable({providedIn: 'root'}) 導出類 MyService { ... }

不過,“新語法”確實提供了一個優勢:Angular 可以延遲加載服務(在幕后),並且可以自動刪除冗余代碼。 這可以帶來更好的性能和加載速度——盡管這通常只適用於更大的服務和應用程序。

除了上述出色的答案之外,如果您的單身人士仍然沒有表現得像單身人士,那么可能還缺少其他一些東西。 我在單例上調用公共函數並發現它使用了錯誤的變量時遇到了這個問題。 事實證明,問題在於對於單例中的任何公共函數,不能保證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$訪問它們this.subscriptableService.onServiceRequested$

親子服務

我在使用不同實例的父服務及其子服務時遇到了麻煩。 要強制使用一個實例,您可以在應用程序模塊提供程序中引用子項作為父項的別名。 父級將無法訪問子級的屬性,但兩個服務將使用相同的實例。 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}
]

應用模塊范圍之外的組件使用的服務

在創建由組件和服務組成的庫時,我遇到了將創建兩個實例的問題。 一個是我的 Angular 項目,一個是我的庫中的組件。 修復:

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>

好吧,Angular 服務的范圍取決於您在根模塊、延遲加載模塊或組件級別提供服務的位置。

這是一個視頻,用真實的例子很好地描述了它。

https://youtu.be/aDyqnQrer3o

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM