在 Angular 中,Service 默认是单例的,但可以通过不同的方式创建多个实例。以下是几种实现多实例 Service 的方法:
1. 在组件级别提供 Service
typescript
// my-service.service.ts
@Injectable()
export class MyService {
private data = Math.random(); // 用于区分不同实例
getData() {
return this.data;
}
}
// component-a.component.ts
@Component({
selector: 'app-component-a',
templateUrl: './component-a.component.html',
providers: [MyService] // 在这里提供,每个组件实例都有自己的 service
})
export class ComponentA {
constructor(public myService: MyService) {}
}
// component-b.component.ts
@Component({
selector: 'app-component-b',
templateUrl: './component-b.component.html',
providers: [MyService] // 这里会创建另一个实例
})
export class ComponentB {
constructor(public myService: MyService) {}
}
2. 使用工厂函数创建不同实例
typescript
// configurable.service.ts
@Injectable()
export class ConfigurableService {
constructor(private config: { prefix: string }) {}
process(value: string): string {
return `${this.config.prefix}: ${value}`;
}
}
// 在模块中注册多个实例
@NgModule({
providers: [
// 实例1
{
provide: 'ServiceInstanceA',
useFactory: () => {
return new ConfigurableService({ prefix: 'InstanceA' });
}
},
// 实例2
{
provide: 'ServiceInstanceB',
useFactory: () => {
return new ConfigurableService({ prefix: 'InstanceB' });
}
}
]
})
export class AppModule {}
// 在组件中注入特定实例
@Component({
selector: 'app-example',
template: `
<div>Service A: {{ resultA }}</div>
<div>Service B: {{ resultB }}</div>
`
})
export class ExampleComponent {
resultA: string;
resultB: string;
constructor(
@Inject('ServiceInstanceA') private serviceA: ConfigurableService,
@Inject('ServiceInstanceB') private serviceB: ConfigurableService
) {
this.resultA = this.serviceA.process('Hello');
this.resultB = this.serviceB.process('World');
}
}
3. 使用 @Injectable({ providedIn: 'any' })
在 Angular 6+ 中,可以使用 providedIn: 'any':
typescript
// any-scope.service.ts
@Injectable({
providedIn: 'any' // 每个懒加载模块都会得到新实例,但模块内是单例
})
export class AnyScopeService {
instanceId = Math.random();
}
// 在不同懒加载模块中使用
// LazyModule1 和 LazyModule2 会得到不同的实例
4. 使用继承创建子类实例
typescript
// base.service.ts
@Injectable()
export class BaseService {
protected baseValue = 'Base';
getValue(): string {
return this.baseValue;
}
}
// 创建多个继承的子类
@Injectable()
export class ExtendedServiceA extends BaseService {
constructor() {
super();
this.baseValue = 'ServiceA';
}
}
@Injectable()
export class ExtendedServiceB extends BaseService {
constructor() {
super();
this.baseValue = 'ServiceB';
}
}
// 在模块中注册
@NgModule({
providers: [
ExtendedServiceA,
ExtendedServiceB
]
})
export class AppModule {}
5. 动态创建实例
typescript
// dynamic-instance.service.ts
@Injectable()
export class DynamicInstanceService {
private static instanceCounter = 0;
private instanceId: number;
constructor() {
this.instanceId = ++DynamicInstanceService.instanceCounter;
}
getId(): number {
return this.instanceId;
}
}
// instance-factory.service.ts
@Injectable({
providedIn: 'root'
})
export class InstanceFactoryService {
createServiceInstance(): DynamicInstanceService {
return new DynamicInstanceService();
}
}
// 使用
@Component({
selector: 'app-dynamic',
template: `
<button (click)="createInstance()">创建新实例</button>
<div *ngFor="let service of services">
实例 ID: {{ service.getId() }}
</div>
`
})
export class DynamicComponent {
services: DynamicInstanceService[] = [];
constructor(private factory: InstanceFactoryService) {}
createInstance() {
const instance = this.factory.createServiceInstance();
this.services.push(instance);
}
}
6. 使用 InjectionToken 和工厂模式
typescript
// 创建 InjectionToken
export const MULTI_SERVICE = new InjectionToken<MultiService[]>('MultiService');
// 服务接口
export interface MultiService {
process(data: string): string;
}
// 实现类
@Injectable()
export class MultiServiceImpl implements MultiService {
private id = Math.random();
process(data: string): string {
return `${this.id}: ${data}`;
}
}
// 注册多个实例
@NgModule({
providers: [
{
provide: MULTI_SERVICE,
useFactory: () => new MultiServiceImpl(),
multi: true // 关键:允许多个 provider
},
{
provide: MULTI_SERVICE,
useFactory: () => new MultiServiceImpl(),
multi: true
},
{
provide: MULTI_SERVICE,
useFactory: () => new MultiServiceImpl(),
multi: true
}
]
})
export class AppModule {}
// 注入所有实例
@Component({
selector: 'app-multi',
template: `
<div *ngFor="let service of services">
{{ service.process('test') }}
</div>
`
})
export class MultiComponent {
services: MultiService[];
constructor(@Inject(MULTI_SERVICE) services: MultiService[]) {
this.services = services; // 获取所有实例的数组
}
}
7. 实际应用场景示例
场景:每个标签页需要独立的数据服务
typescript
// tab-data.service.ts
@Injectable()
export class TabDataService {
private data: any[] = [];
addItem(item: any) {
this.data.push(item);
}
getItems() {
return [...this.data];
}
clear() {
this.data = [];
}
}
// tab.component.ts
@Component({
selector: 'app-tab',
template: `
<div class="tab">
<input [(ngModel)]="newItem" placeholder="输入内容">
<button (click)="addItem()">添加</button>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>
</div>
`,
providers: [TabDataService] // 每个标签页有自己的实例
})
export class TabComponent {
newItem = '';
items: any[] = [];
constructor(private tabDataService: TabDataService) {
this.items = this.tabDataService.getItems();
}
addItem() {
this.tabDataService.addItem(this.newItem);
this.items = this.tabDataService.getItems();
this.newItem = '';
}
}
// tabs-container.component.ts
@Component({
selector: 'app-tabs-container',
template: `
<button (click)="addTab()">添加标签页</button>
<div *ngFor="let tab of tabs; let i = index">
<h3>标签页 {{ i + 1 }}</h3>
<app-tab></app-tab>
</div>
`
})
export class TabsContainerComponent {
tabs: number[] = [1, 2, 3];
addTab() {
this.tabs.push(this.tabs.length + 1);
}
}
8. 注意事项
内存管理
- 组件级别的服务实例会随着组件销毁而销毁
- 手动创建的实例需要手动管理生命周期
性能考虑
- 多个实例会增加内存使用
- 根据实际需求选择合适的方式
依赖注入层级
typescript
// 不同层级的注入器会创建不同的实例
@NgModule() // 模块级单例
@Component() // 组件级实例
@Directive() // 指令级实例
总结
选择哪种方式取决于具体需求:
- 组件级隔离 → 使用
providers: [Service] - 配置不同的实例 → 使用工厂函数
- 模块级多实例 → 使用
providedIn: 'any' - 动态创建 → 使用工厂服务手动创建
- 同一接口多个实现 → 使用
multi: true
最常用的是在组件级别提供 Service,这样每个组件实例都会得到自己的 Service 实例,实现了完全的隔离。
注意事项
如果是急加载模块(非懒加载模块),无论在多少个模块的 providers 中声明,Service 实例都是同一个(单例)。 Angular 的依赖注入系统在应用启动时创建了一个 根注入器(Root Injector) 。所有急加载模块的 providers 都会被合并到根注入器中。
| 模块类型 | 在多个模块的 providers 中声明 | 实例情况 |
|---|---|---|
| 急加载模块 | 是 | 同一个实例(单例) |
| 懒加载模块 | 是 | 不同实例(每个模块新实例) |
| 混合情况 | 急加载+懒加载都有声明 | 急加载用根实例,懒加载用新实例 |
关键点:
- 所有急加载模块共享根注入器
- providers 声明会被合并
- Service 在应用启动时实例化一次
- 要实现多实例,需要使用不同的 Token 或在更细粒度级别提供