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

时间:2016-03-24 11:10:59

标签: angular typescript angular2-routing angular2-services

我已经读过,当引导应该让所有孩子共享相同的实例时注入,但我的主要和头部组件(主应用程序包括头组件和路由器出口)都获得了我的服务的单独实例。

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

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

从我的日志记录看起来,引导调用结束,然后我看到FacebookService然后在每个构造函数运行的代码,MainAppComponent,HeaderComponent和DefaultComponent之前创建UserService:

enter image description here

14 个答案:

答案 0 :(得分:128)

更新(Angular 6)

创建singleton service的推荐方法已更改。现在建议在服务的@Injectable装饰器中指定它应该在“root”中提供。这对我来说很有意义,并且不再需要在模块中列出所有提供的服务。您只需在需要时导入服务,并在适当的位置注册。您也可以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) {}
}

事实上,在'providers'中列出你的类会创建一个新的实例,如果任何父组件已经列出了它,那么孩子们不需要,如果他们这样做,他们将得到一个新的实例。

答案 1 :(得分:119)

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

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

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

当Angular2尝试在组件构造函数中注入某些内容时:

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

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

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

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

答案 2 :(得分:24)

我知道角度有分层注射器,如蒂埃里所说。

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

我们可以通过创建服务实例来实现这一目标,并且在提供时始终返回该服务。

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

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

我不是说这是一种更好的方法,以防万一有人出现无法进行分层注射器的问题。

答案 3 :(得分:16)

语法已更改。请检查此link

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

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

答案 4 :(得分:7)

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

答案 5 :(得分:6)

这似乎对我有用

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

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

答案 6 :(得分:5)

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

答案 7 :(得分:3)

您可以在提供商中使用useValue

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

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

答案 8 :(得分:3)

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

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

}

检查docs here

  

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

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

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

答案 9 :(得分:2)

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

答案 10 :(得分:1)

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

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

  1. 使用providedIn属性,或

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

使用提供的内容

从Angular 6.0开始,创建单例服务的首选方法是将providedIn设置为以该服务的@Injectable()装饰器为根。这告诉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开始,最好在服务本身上使用providedIn装饰器的@Injectable()属性,因为它会使您的服务树状摇动。

答案 11 :(得分:0)

  1. 如果要在应用程序级别使服务单例 您应该在 app.module.ts

    中对其进行定义

    提供者:[     MyApplicationService ] (您也可以在子模块中定义相同的名称,以使其特定于该模块)

    • 请勿在提供程序中为该组件创建实例的实例中添加该服务,该实例会破坏单例概念,而只需通过构造函数进行注入即可。
  2. 如果要在组件级别定义单例服务 创建服务,将该服务添加到app.module.ts中,并在特定组件内添加provider数组,如下面的代码所示。

    @Component({       选择器:“ app-root”,       templateUrl:“ ./ test.component.html”,       styleUrls:['./test.component.scss'],       提供者:[TestMyService]     })

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

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

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

答案 12 :(得分:0)

除了上述出色的答案之外,如果您单身人士中的事物仍然没有表现为单身人士,那么可能还会缺少一些其他内容。在单例上调用公共函数并发现它使用了错误的变量时,我遇到了这个问题。事实证明,问题是不能保证if (ngrok) { console.log('If nGronk') ngrok.connect( { addr: configstrp.ngrok.port, subdomain: configstrp.ngrok.subdomain, authtoken: configstrp.ngrok.authtoken, host_header:3000 }, (err, url) => { if (err) { } else { } } ); } 对于单例中的任何公共功能都绑定到单例。可以按照here的建议进行更正,如下所示:

find ./ -iname '*.!*' -size 0 -delete
for i in */.git; do ( echo $i; cd $i/..; git gc --aggressive --prune=now --force; ); done

或者,您可以简单地将类成员声明为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访问它们。

答案 13 :(得分:0)

父母和儿童服务

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