【问题标题】:How do I create a singleton service in Angular 2?如何在 Angular 2 中创建单例服务?
【发布时间】:2016-07-11 23:03:31
【问题描述】:

我读过在引导时注入应该让所有子级共享同一个实例,但是我的主组件和标头组件(主应用程序包括标头组件和路由器出口)都获得了我的服务的单独实例。

我有一个用于调用 facebook javascript api 的 FacebookService 和一个使用 FacebookService 的 UserService。这是我的引导程序:

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

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

【问题讨论】:

  • 您确定您没有UserServiceFacebookService 添加到providers 的其他任何地方?

标签: angular typescript angular2-routing angular2-services


【解决方案1】:

更新(Angular 6 +)

创建singleton service 的推荐方式已更改。现在建议在服务的@Injectable 装饰器中指定它应该在“根”中提供。这对我来说很有意义,根本不需要在你的模块中列出所有提供的服务。您只需在需要时导入服务,它们就会在适当的位置进行注册。你也可以specify a module,所以只有在模块被导入时才会提供。

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

更新(Angular 2)

使用 NgModule,我认为现在的方法是创建一个包含您的服务类的“CoreModule”,并在模块的提供者中列出该服务。然后在主应用程序模块中导入核心模块,该模块将为在其构造函数中请求该类的任何子级提供一个实例:

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

事实上,在“提供者”中列出你的类会创建它的一个新实例,如果任何父组件已经列出它,那么子组件就不需要,如果他们这样做,他们将获得一个新实例。

【讨论】:

  • 截至目前(2017 年 1 月),您应该在模块文件中的 [providers] 下列出服务,而不是在 bootstrap() 下,对吧?
  • 为什么不把ApiService 放在AppModuleproviders 中,从而避免需要CoreModule? (不确定这是@JasonSwett 的建议。)
  • @JasonGoemaat 您能否添加示例如何在组件中使用它?我们可以在顶部导入ApiService,但是为什么还要费心把它放在 CoreModule 的 providers 数组中,然后在 app.module 中导入那个......它还没有为我点击。跨度>
  • 因此,将服务放在模块提供者上将为该模块提供一个单例。并且将服务放在组件提供者上将为组件的每个实例创建一个新实例?对吗?
  • @BrunoLM 我创建了一个test app 来帮助展示正在发生的事情。有趣的是,即使在 core 和 app 模块中都指定了 TestService,但不会为模块创建实例,因为它是由组件提供的,因此 angular 永远不会在注入器树中占据那么高的位置。因此,如果您在模块中提供服务但从不使用它,则似乎没有创建实例。
【解决方案2】:

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

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

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

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

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

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

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

查看这个问题了解更多详情:

【讨论】:

  • 谢谢,这解释得很好。这对我来说是违反直觉的,因为这有点违反了 Angular 2 的自包含组件范式。假设我正在为 Facebook 创建一个组件库,但我希望它们都使用单例服务。也许有一个组件可以显示登录用户的个人资料图片,另一个要发布。使用这些组件的应用程序必须包含 Facebook 服务作为提供者,即使它本身不使用该服务?我可以只返回一个带有 'getInstance()' 方法的服务,该方法管理真实服务的单例......
  • @tThierryTemplier 我将如何做相反的事情,我有一个公共服务类,我想在多个组件中注入但每次都实例化一个新实例(提供者/指令选项应该被弃用并在下一个版本)
  • 抱歉这么笨,但我不清楚你是如何创建单例服务的,你能详细解释一下吗?
  • 那么,要使用服务的单个实例,它应该在 app.module.ts 还是 app.component.ts 中声明为提供者?
  • 仅在 app.module.ts 中声明每个服务,为我完成了这项工作。
【解决方案3】:

我知道 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]
})

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

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

【讨论】:

  • 组件中的SHARE_SERVICE_PROVIDER应该是YOUR_SERVICE_PROVIDER吗?此外,我假设像正常一样需要导入服务文件,并且构造函数仍将具有“YourService”类型的参数,对吗?我认为我喜欢这个,允许您保证单例,而不必确保在层次结构中提供服务。它还允许单个组件通过在providers 中列出服务而不是单例提供程序来获取自己的副本,对吗?
  • @JasonGoemaat 你是对的。编辑了那个。确切地说,您在构造函数和添加YOUR_SERVICE_PROVIDER 的该组件的提供者上以完全相同的方式执行此操作。是的,所有组件都将获得相同的实例,只需将其添加到提供程序中即可。
  • +1 虽然这是一种创建单例服务的方法,但它非常适合作为创建multiton 服务的一种方式,只需将instance 属性更改为实例的键值映射
  • @RyNo 我可以想象一个不需要为每条路由提供服务的应用程序,或者一个需要静态实例并希望将同一实例与使用它的任何其他模块一起使用的可重用模块。也许是创建与服务器的 Web 套接字连接并处理消息的东西。也许应用程序中只有少数路由想要使用它,因此如果不需要,在应用程序启动时无需创建服务实例和 Web 套接字连接。您可以围绕它进行编程,让组件在使用它的任何地方“初始化”服务,但按需单例会很有用。
  • 这个答案应该有 999999 票,史诗级的东西,非常感谢!
【解决方案4】:

语法已更改。检查这个link

依赖是注入器范围内的单例。在下面的例子中,一个 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 类不是服务,而只包含供全球使用的static 字符串怎么办?
  • 喜欢这个提供者:[{provide:'API_URL',useValue:'coolapi.com'}]
【解决方案5】:

这似乎对我很有效

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

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

【讨论】:

  • 我称之为 Angular2 反模式。正确提供服务,Angular2 将始终注入相同的实例。另见stackoverflow.com/questions/12755539/…
  • @Günter Zöchbauer,能否就“正确提供服务,Angular2 将始终注入相同的实例”提供一些建议。 ?因为不清楚,我通过谷歌搜索找不到任何有用的信息。
  • 我刚刚发布了这个答案,可能有助于解决您的问题stackoverflow.com/a/38781447/217408(另请参阅那里的链接)
  • 这是完美的。您应该使用 angulars 自己的依赖注入,但是使用这种模式来绝对确定您的服务是单例的,当您期望它是单例时,它并没有什么坏处。仅仅因为您在两个不同的地方注入相同的服务,就有可能节省大量寻找错误的时间。
  • 我使用这个模式来确定我面临的问题是因为服务不是单例
【解决方案6】:

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

【讨论】:

  • 如果我理解它,请告诉我。如果我按照你说的做,好吧,这将是一个单身人士。除此之外,如果服务也是另一个模块中的提供者,它就不再是单例了,对吧?因为层次结构。
  • 并且不要在页面的@Component装饰器中注册提供者。
  • @劳拉。我是否仍将其导入到实际使用该服务的组件中?
  • @Mark 是的,你需要导入它,然后你只需要在constructor这样声明它:import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
【解决方案7】:

这是 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");
        });
    }
    ...
}

【讨论】:

    【解决方案8】:

    您可以在提供程序中使用useValue

    import { MyService } from './my.service';
    
    @NgModule({
    ...
      providers: [ { provide: MyService, useValue: new MyService() } ],
    ...
    })
    

    【讨论】:

    • useValue 与单例无关。 Usevalue只是传递一个值而不是DI调用newuseFactoryTypeuseClass),其中传递了一个函数,该函数在DI调用时返回该值。 Angular DI 自动为每个提供者维护一个实例。只提供一次,你就有一个单身人士。抱歉,我不得不投反对票,因为它只是无效信息:-/
    【解决方案9】:

    在 Angular@6 中,您可以在 Injectable 中包含 providedIn

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

    查看docs here

    在 Angular 中有两种方法可以使服务成为单例:

    1. 声明应在应用程序根目录中提供服务。
    2. 将服务包含在 AppModule 或仅由 AppModule 导入的模块中。

    从 Angular 6.0 开始,创建单例服务的首选方式是在服务上指定它应该在应用程序根目录中提供。这是通过在服务的 @Injectable 装饰器上将 providedIn 设置为 root 来完成的:

    【讨论】:

    • 这很好,但您也可能遇到变量不存在的意外问题,可以通过声明某些项目public static 来解决。
    【解决方案10】:

    仅在 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;
      }
    }
    

    【讨论】:

      【解决方案11】:

      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 属性,因为它使您的服务可摇树。

      【讨论】:

        【解决方案12】:
        1. 如果您想在应用程序级别制作服务单例 你应该在 app.module.ts

          中定义它

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

          • 不要在提供程序中添加此服务,因为提供程序会为该组件创建一个实例,这会破坏单例概念,只需通过构造函数注入即可。
        2. 如果你想在组件级别定义单例服务 创建服务,在 app.module.ts 中添加该服务,并在特定组件内添加 providers 数组,如下所示。

          @组件({ 选择器:'应用程序根', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], 提供者:[TestMyService] })

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

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

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

        【讨论】:

          【解决方案13】:

          除了上述出色的答案之外,如果您的单例中的东西仍然没有像单例那样表现,则可能还缺少其他一些东西。我在单例上调用公共函数并发现它使用了错误的变量时遇到了这个问题。事实证明,问题在于 this 不能保证绑定到单例中的任何公共函数的单例。这可以通过遵循here 的建议来纠正,如下所示:

          @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$

          【讨论】:

            【解决方案14】:

            父子服务

            我在使用不同实例的父服务及其子服务时遇到问题。要强制使用一个实例,您可以在应用程序模块提供程序中为父级引用子级的别名。父级将无法访问子级的属性,但两个服务将使用同一个实例。 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>
            

            【讨论】:

            • 您的意思是回答您自己的问题吗?如果是这样,您可以在发布问题后将答案剪切/粘贴到答案字段,从而将答案分离为 StackOverflow 上的正式答案。
            【解决方案15】:

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

            这是一个视频,用真实的例子很好地描述了它。

            https://youtu.be/aDyqnQrer3o

            【讨论】:

            • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
            • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
            猜你喜欢
            • 1970-01-01
            • 2019-06-21
            • 2023-03-25
            • 1970-01-01
            • 2019-01-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-02-14
            相关资源
            最近更新 更多