简体   繁体   English

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

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

I've read that injecting when bootstrapping should have all children share the same instance, but my main and header components (main app includes header component and router-outlet) are each getting a separate instance of my services.我读过在引导时注入应该让所有孩子共享同一个实例,但是我的主要和头组件(主应用程序包括头组件和路由器出口)每个都获得了我的服务的单独实例。

I have a FacebookService which I use to make calls to the facebook javascript api and a UserService that uses the FacebookService.我有一个 FacebookService,我用它来调用 facebook javascript api 和一个使用 FacebookService 的 UserService。 Here's my bootstrap:这是我的引导程序:

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

From my logging it looks like the bootstrap call finishes, then I see the FacebookService then UserService being created before the code in each of the constructors runs, the MainAppComponent, the HeaderComponent and the DefaultComponent:从我的日志记录来看,引导程序调用完成,然后我看到 FacebookService 然后 UserService 在每个构造函数中的代码运行之前创建,MainAppComponent、HeaderComponent 和 DefaultComponent:

在此处输入图片说明

Update (Angular 6 +)更新(Angular 6 +)

The recommended way to create a 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'.现在建议在服务的@Injectable装饰器中指定它应该在“根”中提供。 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 so it will only be provided if the module is imported.您还可以指定一个模块,以便仅在导入该模块时才提供该模块。

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

Update (Angular 2)更新(角度 2)

With NgModule, the way to do it now I think is to create a 'CoreModule' with your service class in it, and list the service in the module's providers.使用 NgModule,我认为现在的方法是创建一个包含您的服务类的“CoreModule”,并在模块的提供者中列出服务。 Then you import the core module in your main app module which will provide the one instance to any children requesting that class in their constructors:然后在主应用程序模块中导入核心模块,该模块将为在其构造函数中请求该类的任何子项提供一个实例:

CoreModule.ts核心模块.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 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 { }

Original Answer原答案

If you list a provider in bootstrap() , you don't need to list them in your component decorator:如果您在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) {}
}

In fact listing your class in 'providers' creates a new instance of it, if any parent component already lists it then the children don't need to, and if they do they will get a new instance.事实上,在 'providers' 中列出你的类会创建它的一个新实例,如果任何父组件已经列出它,那么子组件不需要,如果他们这样做,他们将获得一个新实例。

Jason is completely right!杰森完全正确! It's caused by the way dependency injection works.这是由依赖注入的工作方式引起的。 It's based on hierarchical injectors.它基于分层注入器。

There are several injectors within an Angular2 application: Angular2 应用程序中有几个注入器:

  • The root one you configure when bootstrapping your application引导应用程序时配置的根目录
  • An injector per component.每个组件一个注射器。 If you use a component inside another one.如果您在另一个组件中使用一个组件。 The component injector is a child of the parent component one.组件注入器是父组件的一个子组件。 The application component (the one you specify when boostrapping your application) has the root injector as parent one).应用程序组件(您在 boostrap 应用程序时指定的那个)将根注入器作为父注入器)。

When Angular2 tries to inject something in the component constructor:当 Angular2 尝试在组件构造函数中注入一些东西时:

  • It looks into the injector associated with the component.它查看与组件关联的注入器。 If there is matching one, it will use it to get the corresponding instance.如果有匹配的,它将使用它来获取相应的实例。 This instance is lazily created and is a singleton for this injector.此实例是延迟创建的,并且是此注入器的单例。
  • If there is no provider at this level, it will look at the parent injector (and so on).如果此级别没有提供程序,它将查看父注入器(等等)。

So if you want to have a singleton for the whole application, you need to have the provider defined either at the level of the root injector or the application component injector.因此,如果您想为整个应用程序创建一个单例,则需要在根注入器或应用程序组件注入器级别定义提供程序。

But Angular2 will look at the injector tree from the bottom.但是 Angular2 会从底部查看注入器树。 This means that the provider at the lowest level will be used and the scope of the associated instance will be this level.这意味着将使用最低级别的提供程序,并且关联实例的范围将是此级别。

See this question for more details:有关更多详细信息,请参阅此问题:

I know angular has hierarchical injectors like Thierry said.我知道 angular 有像 Thierry 所说的分层注入器。

But I have another option here in case you find a use-case where you don't really want to inject it at the parent.但是,如果您发现一个用例,您真的不想将它注入到父级,我在这里还有另一个选择。

We can achieve that by creating an instance of the service, and on provide always return that.我们可以通过创建服务的实例来实现这一点,并在提供时始终返回该实例。

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

And then on your component you use your custom provide method.然后在您的组件上使用您的自定义提供方法。

@Component({
  providers: [YOUR_SERVICE_PROVIDER]
})

And you should have a singleton service without depending on the hierarchical injectors.并且您应该拥有一个不依赖分层注入器的单例服务。

I'm not saying this is a better way, is just in case someone has a problem where hierarchical injectors aren't possible.我并不是说这是一种更好的方法,只是以防万一有人遇到无法使用分层注入器的问题。

Syntax has been changed.语法已更改。 Check this link检查此链接

Dependencies are singletons within the scope of an injector.依赖项是注入器范围内的单例。 In below example, a single HeroService instance is shared among the HeroesComponent and its HeroListComponent children.在下面的示例中,单个 HeroService 实例在 HeroesComponent 及其 HeroListComponent 子项之间共享。

Step 1. Create singleton class with @Injectable decorator步骤 1. 使用 @Injectable 装饰器创建单例类

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

Step 2. Inject in constructor步骤 2. 注入构造函数

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

Step 3. Register provider步骤 3. 注册提供商

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

this seems to be working well for me这对我来说似乎很有效

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

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

添加@Injectable装饰到服务,登记其作为根模块提供商将使一个单身。

Here is a working example with Angular version 2.3.这是一个使用 Angular 2.3 版的工作示例。 Just call the constructor of the service the stand way like this constructor(private _userService:UserService) .只需像这个构造函数(private _userService:UserService) 那样以stand 方式调用服务的构造函数。 And it will create a singleton for the app.它将为应用程序创建一个单例。

user.service.ts用户服务.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 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");
        });
    }
    ...
}

You can use useValue in providers您可以在提供者中使用useValue

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

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

From Angular@6, you can have providedIn in an Injectable .从 Angular@6 开始,您可以在Injectable使用providedIn

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

}

Check the docs here检查这里文档

There are two ways to make a service a singleton in Angular:在 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.将服务包含在 AppModule 或仅由 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.从 Angular 6.0 开始,创建单例服务的首选方法是在服务上指定它应该在应用程序根中提供。 This is done by setting providedIn to root on the service's @Injectable decorator:这是通过将 providedIn 设置为服务的 @Injectable 装饰器上的 root 来完成的:

Just declare your service as provider in app.module.ts only.只需在 app.module.ts 中将您的服务声明为提供者即可。

It did the job for me.它为我完成了工作。

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

then either instanciate it using a constructor private parameter :然后使用构造函数私有参数实例化它:

constructor(private topicService: TopicService) { }

or since if your service is used from html, the -prod option will claim:或者因为如果您的服务是从 html 使用的,则 -prod 选项将声明:

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

add a member for your service and fill it with the instance recieved in the constructor:为您的服务添加一个成员,并用构造函数中收到的实例填充它:

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

A singleton service is a service for which only one instance exists in an app. singleton service是一种应用程序中仅存在一个实例的服务。

There are (2) ways to provide a singleton service for your application.(2) 种方法可以为您的应用程序提供单例服务。

  1. use the providedIn property, or使用providedIn属性,或

  2. provide the module directly in the AppModule of the application直接在应用的AppModule中提供模块

Using providedIn使用提供的输入

Beginning with Angular 6.0, the preferred way to create a singleton service is to set providedIn to root on the service's @Injectable() decorator.从 Angular 6.0 开始,创建单例服务的首选方法是在服务的@Injectable()装饰器@Injectable() providedIn为 root。 This tells Angular to provide the service in the application root.这告诉 Angular 在应用程序根中提供服务。

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

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

NgModule providers array NgModule 提供者数组

In apps built with Angular versions prior to 6.0, services are registered NgModule providers arrays as follows:在使用 Angular 6.0 之前版本构建的应用程序中,服务注册 NgModule 提供程序数组如下:

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

If this NgModule were the root AppModule , the UserService would be a singleton and available throughout the app.如果此NgModule是根AppModule ,则 UserService 将是一个单例并在整个应用程序中可用。 Though you may see it coded this way, using the providedIn property of the @Injectable() decorator on the service itself is preferable as of Angular 6.0 as it makes your services tree-shakable.尽管您可能会看到它是这样编码的, @Injectable() Angular 6.0 开始,在服务本身上使用@Injectable()装饰器的providedIn属性是更可取的,因为它使您的服务可以摇树。

  1. If you want to make service singleton at application level you should define it in app.module.ts如果你想在应用程序级别制作服务单例,你应该在app.module.ts 中定义它

    providers: [ MyApplicationService ] (you can define the same in child module as well to make it that module specific)提供者:[ MyApplicationService ](您也可以在子模块中定义相同的内容以使其特定于该模块)

    • Do not add this service in provider which creates an instance for that component which breaks the singleton concept, just inject through constructor.不要在为那个打破单例概念的组件创建实例的 provider 中添加这个服务,只需通过构造函数注入。
  2. If you want to define singleton service at component level create service, add that service in app.module.ts and add in providers array inside specific component as shown in below snipet.如果您想在组件级别定义单例服务创建服务,请在 app.module.ts 中添加该服务,并在特定组件内添加 providers 数组,如下面的代码片段所示。

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

  3. Angular 6 provide new way to add service at application level. Angular 6 提供了在应用程序级别添加服务的新方法。 Instead of adding a service class to the providers[] array in AppModule , you can set the following config in @Injectable() :您可以在 @Injectable() 中设置以下配置,而不是向 AppModule 中的 providers[] 数组添加服务类:

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

The "new syntax" does offer one advantage though: Services can be loaded lazily by Angular (behind the scenes) and redundant code can be removed automatically.不过,“新语法”确实提供了一个优势:Angular 可以延迟加载服务(在幕后),并且可以自动删除冗余代码。 This can lead to a better performance and loading speed - though this really only kicks in for bigger services and apps in general.这可以带来更好的性能和加载速度——尽管这通常只适用于更大的服务和应用程序。

In addition to the above excellent answers, there may be something else that is missing if things in your singleton still aren't behaving as a singleton.除了上述出色的答案之外,如果您的单身人士仍然没有表现得像单身人士,那么可能还缺少其他一些东西。 I ran into the issue when calling a public function on the singleton and finding that it was using the wrong variables.我在单例上调用公共函数并发现它使用了错误的变量时遇到了这个问题。 It turns out that the problem was the this isn't guaranteed to be bound to the singleton for any public functions in the singleton.事实证明,问题在于对于单例中的任何公共函数,不能保证this绑定到单例。 This can be corrected by following the advice here , like so:这可以通过遵循此处的建议来纠正,如下所示:

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

Alternatively, you can simply declare the class members as public static instead of public , then the context won't matter, but you'll have to access them like SubscriptableService.onServiceRequested$ instead of using dependency injection and accessing them via this.subscriptableService.onServiceRequested$ .或者,您可以简单地将类成员声明为public static而不是public ,然后上下文无关紧要,但是您必须像SubscriptableService.onServiceRequested$一样访问它们,而不是使用依赖注入并通过this.subscriptableService.onServiceRequested$访问它们this.subscriptableService.onServiceRequested$

Parent and child services亲子服务

I was having trouble with a parent service and its child using different instances.我在使用不同实例的父服务及其子服务时遇到了麻烦。 To force one instance to be used, you can alias the parent with reference to the child in your app module providers.要强制使用一个实例,您可以在应用程序模块提供程序中引用子项作为父项的别名。 The parent will not be able to access the child's properties, but the same instance will be used for both services.父级将无法访问子级的属性,但两个服务将使用相同的实例。 https://angular.io/guide/dependency-injection-providers#aliased-class-providers https://angular.io/guide/dependency-injection-providers#aliased-class-providers

app.module.ts app.module.ts

providers: [
  ChildService,
  // Alias ParentService w/ reference to ChildService
  { provide: ParentService, useExisting: ChildService}
]

Services used by components outside of your app modules scope应用模块范围之外的组件使用的服务

When creating a library consisting of a component and a service, I ran into an issue where two instances would be created.在创建由组件和服务组成的库时,我遇到了将创建两个实例的问题。 One by my Angular project and one by the component inside of my library.一个是我的 Angular 项目,一个是我的库中的组件。 The fix:修复:

my-outside.component.ts my-outside.component.ts

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

my-inside.component.ts my-inside.component.ts

  constructor(public myService: MyOutsideService) { }

my-inside.component.hmtl my-inside.component.hmtl

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

Well, Scope of Angular service depends upon where you provide the service at root module, lazy loaded module or at component level.好吧,Angular 服务的范围取决于您在根模块、延迟加载模块或组件级别提供服务的位置。

Here is a video which describe it beutifully with real examples.这是一个视频,用真实的例子很好地描述了它。

https://youtu.be/aDyqnQrer3o https://youtu.be/aDyqnQrer3o

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM