如何在 Angular 2 中创建单例服务?

How do I create a singleton service in Angular 2?

我读到在 bootstrapping 时注入应该让所有 children 共享同一个实例,但我的主要和 header 组件(主要应用程序包括 header component 和 router-outlet) 都获得了我的服务的一个单独实例。

我有一个用于调用 facebook javascript api 的 FacebookService 和一个使用 FacebookService 的 UserService。这是我的 bootstrap:

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

从我的记录来看,bootstrap 调用似乎完成了,然后我看到在每个构造函数中的代码运行之前创建了 FacebookService,然后是 UserService:MainAppComponent、HeaderComponent 和 DefaultComponent:

更新 (Angular 6 +)

推荐的创建方法 singleton service has changed. It is now recommended to specify in the @Injectable decorator on the service that it should be provided in the 'root'. This makes a lot of sense to me and there's no need to list all the provided services in your modules at all anymore. You just import the services when you need them and they register themselves in the proper place. You can also specify a module 因此只有在导入模块时才会提供。

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

更新 (Angular 2)

对于 NgModule,我认为现在的方法是创建一个 'CoreModule' 并在其中包含您的服务 class,并在模块的提供程序中列出该服务。然后你在你的主应用程序模块中导入核心模块,它将为任何 children 在其构造函数中请求 class 提供一个实例:

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' 中列出您的 class 会创建它的一个新实例,如果任何 parent 组件已经列出它,那么 children 不需要,如果他们这样做,他们将获得一个新实例。

杰森完全正确!这是由依赖注入的工作方式引起的。它基于分层注入器。

Angular2 应用程序中有多个注入器:

  • 引导应用程序时配置的根目录
  • 每个组件一个喷油器。如果您在另一个组件中使用一个组件。组件注入器是父组件注入器的子组件。应用程序组件(您在引导应用程序时指定的组件)将根注入器作为父注入器)。

当 Angular2 试图在组件构造函数中注入一些东西时:

  • 它查看与组件关联的注入器。如果有匹配的,它将使用它来获取相应的实例。此实例是延迟创建的,是此注入器的单例。
  • 如果此级别没有提供者,它将查看父注入器(依此类推)。

因此,如果您想为整个应用程序使用单例,则需要在根注入器或应用程序组件注入器级别定义提供者。

但是 Angular2 会从底部查看注入器树。这意味着将使用最低级别的提供程序,关联实例的范围将是该级别。

有关详细信息,请参阅此问题:

  • What's the best way to inject one service into another in angular 2 (Beta)?

我知道 angular 有像 Thierry 所说的分层注入器。

但是我在这里还有另一个选择,以防你发现一个你真的不想在父级注入它的用例。

我们可以通过创建服务实例来实现,并始终提供 return。

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

而且你应该有一个不依赖分层注入器的单例服务。

我并不是说这是更好的方法,只是以防万一有人遇到无法使用分层注入器的问题。

这对我来说似乎很有效

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

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

语法已更改。检查这个 link

依赖项是注入器范围内的单例。在下面的示例中,单个 HeroService 实例在 HeroesComponent 及其子 HeroListComponent 之间共享。

第 1 步。使用@Injectable 装饰器

创建单例class
@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 { }

这是 Angular 2.3 版的工作示例。只需像这个 constructor(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");
        });
    }
    ...
}

您可以在供应商

中使用useValue
import { MyService } from './my.service';

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

向服务添加 @Injectable 装饰器,AND 将其注册为根模块中的提供者将使它成为单例。

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

从 Angular@6 开始,您可以在 Injectable 中拥有 providedIn

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

}

勾选docs here

There are two ways to make a service a singleton in Angular:

  1. Declare that the service should be provided in the application root.
  2. Include the service in the AppModule or in a module that is only imported by the AppModule.

Beginning with Angular 6.0, the preferred way to create a singleton services is to specify on the service that it should be provided in the application root. This is done by setting providedIn to root on the service's @Injectable decorator:

  1. 如果你想做应用层的服务单例 你应该在 app.module.ts

    中定义它

    供应商:[ 我的应用服务 ] (您也可以在子模块中定义相同的内容以使其特定于该模块)

    • 不要在 provider 中添加此服务,它会为该组件创建实例,这会破坏单例概念,只需通过构造函数注入即可。
  2. 如果你想在组件级别定义单例服务 创建服务,在 app.module.ts 中添加该服务,并在特定组件内添加 providers 数组,如下面的代码片段所示。

    @组件({ 选择器:'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], 提供商:[TestMyService] })

  3. Angular 6 提供在应用层添加服务的新方法。 您可以在 @Injectable() 中设置以下配置,而不是将服务 class 添加到 AppModule 中的 providers[] 数组:

    @Injectable({providedIn: 'root'}) 导出 class MyService { ... }

"new syntax" 确实提供了一项优势:Angular 可以延迟加载服务(在幕后),并且可以自动删除冗余代码。这可以带来更好的性能和加载速度——尽管这通常只适用于更大的服务和应用程序。

除了上述出色的答案外,如果您的单例中的某些内容仍未表现得像单例,则可能还缺少其他内容。当在单例上调用 public 函数并发现它使用了错误的变量时,我 运行 陷入了这个问题。事实证明,问题是 this 不是 gua运行teed 绑定到单例中的任何 public 函数。这可以通过遵循建议 来纠正,如下所示:

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

或者,您可以简单地将 class 成员声明为 public static 而不是 public,那么上下文无关紧要,但您必须像 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 项目,一个是我库中的组件。修复:

我的-outside.component.ts

@Component({...})
export class MyOutsideComponent {
  @Input() serviceInstance: MyOutsideService;
  ...
}

我的-inside.component.ts

  constructor(public myService: MyOutsideService) { }

我的-inside.component.hmtl

<app-my-outside [serviceInstance]="myService"></app-my-outside>

singleton service 是一项服务,在一个应用程序中只有一个实例。

(2) 种方法 可以为您的应用程序提供单例服务。

  1. 使用providedIn属性,或者

  2. 直接在应用AppModule中提供模块

使用 providedIn

从 Angular 6.0 开始,创建单例服务的首选方法是在服务的 @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 将是单例且在整个应用程序中可用。尽管您可能会看到它以这种方式编码,但从 Angular 6.0 开始,在服务本身上使用 @Injectable() 装饰器的 providedIn 属性 是更可取的,因为它使您的服务树-可动摇。

好吧,Angular 服务的范围取决于您在根模块、惰性加载模块或组件级别提供服务的位置。

这里有一段视频,用真实的例子很好地描述了它。

https://youtu.be/aDyqnQrer3o