Angular的Service创建多个实例的总结

在 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()            // 指令级实例

总结

选择哪种方式取决于具体需求:

  1. 组件级隔离 → 使用 providers: [Service]
  2. 配置不同的实例 → 使用工厂函数
  3. 模块级多实例 → 使用 providedIn: 'any'
  4. 动态创建 → 使用工厂服务手动创建
  5. 同一接口多个实现 → 使用 multi: true

最常用的是在组件级别提供 Service,这样每个组件实例都会得到自己的 Service 实例,实现了完全的隔离。

注意事项

如果是急加载模块(非懒加载模块),无论在多少个模块的 providers 中声明,Service 实例都是同一个(单例)。 Angular 的依赖注入系统在应用启动时创建了一个 根注入器(Root Injector) 。所有急加载模块的 providers 都会被合并到根注入器中。

模块类型 在多个模块的 providers 中声明 实例情况
急加载模块 同一个实例(单例)
懒加载模块 不同实例(每个模块新实例)
混合情况 急加载+懒加载都有声明 急加载用根实例,懒加载用新实例

关键点:

  • 所有急加载模块共享根注入器
  • providers 声明会被合并
  • Service 在应用启动时实例化一次
  • 要实现多实例,需要使用不同的 Token 或在更细粒度级别提供
相关推荐
十五喵1 小时前
智慧物业|物业管理|基于SprinBoot+vue的智慧物业管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·智慧物业管理系统
特级业务专家1 小时前
React vs Vue 调度机制深度剖析:从源码到事件循环的完整解读
前端
ze_juejin1 小时前
Angular中懒加载模块的加载顺序总结
前端
天蓝色的鱼鱼1 小时前
写Tailwind CSS像在写屎山?这锅该不该它背
前端·css
#做一个清醒的人1 小时前
【Electron】IpcMainEvent 参数使用总结
前端·electron
月弦笙音1 小时前
【包管理器】pnpm、npm、cnpm、yarn 深度对比
前端
吹水一流1 小时前
微信小程序页面栈:从一个 Bug 讲到彻底搞懂
前端·微信小程序
j***82702 小时前
【MyBatisPlus】MyBatisPlus介绍与使用
android·前端·后端
Python大数据分析@2 小时前
我把pdfplumber整成了可以拖拉拽的web软件
前端·pdf