我读过在引导时注入应该让所有孩子共享同一个实例,但是我的主组件和标头组件(主应用程序包括标头组件和路由器出口)都获得了我的服务的单独实例。
我有一个用于调用 facebook javascript api 的 FacebookService 和一个使用 FacebookService 的 UserService。这是我的引导程序:
bootstrap(MainAppComponent, [ROUTER_PROVIDERS, UserService, FacebookService]);
从我的日志来看,引导调用似乎完成了,然后我看到 FacebookService 然后在每个构造函数中的代码运行之前创建了 UserService,MainAppComponent、HeaderComponent 和 DefaultComponent:
https://i.stack.imgur.com/6q6tG.png
UserService
和 FacebookService
添加到 providers
的其他任何位置?
更新(Angular 6 +)
创建 singleton service 的推荐方法已更改。现在建议在服务的 @Injectable
装饰器中指定它应该在“根”中提供。这对我来说很有意义,根本不需要在你的模块中列出所有提供的服务。您只需在需要时导入服务,它们就会在适当的位置进行注册。您也可以 specify a module 以便仅在导入模块时提供。
@Injectable({
providedIn: 'root',
})
export class ApiService {
}
https://angular.io/guide/singleton-services
https://angular.io/guide/providers
更新(角度 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 { }
应用模块.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) {}
}
事实上,在“提供者”中列出你的类会创建它的一个新实例,如果任何父组件已经列出它,那么子组件就不需要,如果他们这样做,他们将获得一个新实例。
杰森完全正确!这是由依赖注入的工作方式引起的。它基于分层注入器。
Angular2 应用程序中有几个注入器:
引导应用程序时配置的根目录
每个组件一个注射器。如果您在另一个组件中使用一个组件。组件注入器是父组件的子组件。应用程序组件(您在提升应用程序时指定的组件)将根注入器作为父注入器)。
当 Angular2 尝试在组件构造函数中注入一些东西时:
它查看与组件关联的注入器。如果有匹配的,它将使用它来获取相应的实例。这个实例是延迟创建的,并且是这个注入器的一个单例。
如果此级别没有提供者,它将查看父级注入器(依此类推)。
因此,如果您想为整个应用程序创建一个单例,您需要在根注入器或应用程序组件注入器级别定义提供程序。
但是 Angular2 会从底部查看注入器树。这意味着将使用最低级别的提供程序,并且关联实例的范围将是此级别。
有关更多详细信息,请参阅此问题:
在 Angular 2(Beta)中将一项服务注入另一项服务的最佳方法是什么?
我知道 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
中列出服务而不是单例提供程序来获取自己的副本,对吗?
YOUR_SERVICE_PROVIDER
的组件的提供者上以完全相同的方式执行此操作。是的,只需将其添加到提供程序中,所有组件都将获得相同的实例。
instance
属性更改为实例的键值映射
语法已更改。检查此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
字符串供全球使用怎么办?
这似乎对我很有效
@Injectable()
export class MyStaticService {
static instance: MyStaticService;
constructor() {
return MyStaticService.instance = MyStaticService.instance || this;
}
}
将 @Injectable
装饰器添加到服务中,AND 将其注册为根模块中的提供者将使其成为单例。
constructor
中声明它:import { SomeService } from '../../services/some/some'; @Component({ selector: 'page-selector', templateUrl: 'page.html', }) export class SomePage { constructor( public someService: SomeService ) { }
这是 Angular 2.3 版的一个工作示例。只需像这个构造函数(private _userService:UserService) 一样调用服务的构造函数。它将为应用程序创建一个单例。
用户服务.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() } ],
...
})
useValue
与单例无关。 Usevalue 只是传递一个值,而不是 DI 调用 new
或 useFactory
的 Type
(useClass
),其中传递了一个函数,该函数在 DI 调用时返回该值。 Angular DI 自动为每个提供者维护一个实例。只提供一次,你就有一个单身人士。抱歉,我不得不投反对票,因为它只是无效信息:-/
在 Angular@6 中,您可以在 Injectable
中包含 providedIn
。
@Injectable({
providedIn: 'root'
})
export class UserService {
}
检查 docs here
在 Angular 中有两种方法可以使服务成为单例: 声明服务应该在应用程序根目录中提供。将服务包含在 AppModule 或仅由 AppModule 导入的模块中。从 Angular 6.0 开始,创建单例服务的首选方法是在服务上指定它应该在应用程序根目录中提供。这是通过在服务的 @Injectable 装饰器上将 providedIn 设置为 root 来完成的:
public static
来解决。
只需在 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) 种方法可以为您的应用程序提供单例服务。
使用providedIn属性,或者直接在应用的AppModule中提供模块
使用提供的
从 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
属性,因为它使您的服务可摇树。
如果您想在应用程序级别制作服务单例,您应该在 app.module.ts 提供者中定义它:[ MyApplicationService ](您也可以在子模块中定义相同的内容以使其特定于该模块)不要在提供者中添加此服务它为打破单例概念的组件创建一个实例,只需通过构造函数注入。如果您想在组件级别定义单例服务创建服务,请将该服务添加到 app.module.ts 中,并在特定组件内添加 providers 数组,如下面的片段所示。 @Component({ selector: 'app-root', templateUrl: './test.component.html', styleUrls: ['./test.component.scss'], providers : [TestMyService] }) Angular 6 提供了新的方式在应用程序级别添加服务。您可以在 @Injectable() 中设置以下配置,而不是在 AppModule 中的 providers[] 数组中添加服务类: @Injectable({providedIn: 'root'}) export class MyService { ... }
不过,“新语法”确实提供了一个优势:Angular(在幕后)可以延迟加载服务,并且可以自动删除冗余代码。这可以带来更好的性能和加载速度——尽管这实际上只适用于更大的服务和应用程序。
除了上述出色的答案之外,如果您的单身人士中的事情仍然没有表现得像单身人士一样,那么可能还缺少其他一些东西。我在单例上调用公共函数并发现它使用了错误的变量时遇到了这个问题。事实证明,问题在于对于单例中的任何公共函数,不能保证 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$
一样访问它们,而不是使用依赖注入并通过 {4 访问它们}。
亲子服务
我在使用不同实例的父服务及其子服务时遇到问题。要强制使用一个实例,您可以在应用模块提供程序中为父级引用子级的别名。父级将无法访问子级的属性,但两个服务将使用同一个实例。 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;
...
}
我的内部.component.ts
constructor(public myService: MyOutsideService) { }
我的内部.component.hmtl
<app-my-outside [serviceInstance]="myService"></app-my-outside>
嗯,Angular 服务的范围取决于您在根模块、延迟加载模块或组件级别提供服务的位置。
这是一个视频,它用真实的例子很好地描述了它。
[providers]
下列出该服务,而不是在bootstrap()
中,对吗?ApiService
放在AppModule
的providers
中,从而避免需要CoreModule
? (不确定这是@JasonSwett 的建议。)ApiService
,但是为什么还要费心把它放在 CoreModule 的 providers 数组中,然后在 app.module 中导入那个......它还没有为我点击。TestService
,但不会为模块创建实例,因为它是由组件提供的,因此 Angular 永远不会在注入器树中占据那么高的位置。因此,如果您在模块中提供服务并且从不使用它,则似乎没有创建实例。