You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular依赖注入类:如何在单类中存储不同JSON数据?

解决方案:无需为每个JSON文件创建单独仓库类

你完全不用为每个物种编写单独的仓库子类,Angular的DI体系下有两种更优雅的方式解决这个问题:


方案一:工厂模式(推荐,贴合你的仓库设计思路)

通过一个工厂服务来创建不同物种对应的SpeciesRepository实例,每个实例独立维护对应物种的数据,既保留DI的优势,又能实现多实例隔离。

步骤1:改造基础仓库类

去掉@Injectable(因为它由工厂实例化,而非DI容器直接管理),将speciesType作为构造参数传入:

export class SpeciesRepository {
  private data: string[] | null = null;
  private loadPromise: Promise<void> | null = null;

  constructor(
    private httpClient: HttpClient,
    private readonly speciesType: string
  ) {}

  // 确保数据仅加载一次
  async load(): Promise<void> {
    if (this.loadPromise) return this.loadPromise;
    if (this.data) return;

    this.loadPromise = this.httpClient.get<string[]>(`assets/${this.speciesType}.json`)
      .toPromise()
      .then(data => {
        this.data = data;
        this.loadPromise = null;
      })
      .catch(err => {
        this.loadPromise = null;
        throw err;
      });
    return this.loadPromise;
  }

  getByIndex(index: number): string | undefined {
    return this.data?.[index];
  }
}

步骤2:创建仓库工厂服务

用DI管理工厂,工厂负责生成对应物种的仓库实例:

@Injectable({ providedIn: 'root' })
export class SpeciesRepositoryFactory {
  constructor(private httpClient: HttpClient) {}

  create(speciesType: string): SpeciesRepository {
    return new SpeciesRepository(this.httpClient, speciesType);
  }
}

步骤3:在组件/服务中使用

比如地牢生成组件预加载数据:

@Component({ /* 组件配置 */ })
export class GenerateDungeonComponent {
  private humanRepo: SpeciesRepository;
  private dogRepo: SpeciesRepository;
  private catRepo: SpeciesRepository;

  constructor(private repoFactory: SpeciesRepositoryFactory) {
    this.humanRepo = repoFactory.create('human');
    this.dogRepo = repoFactory.create('dog');
    this.catRepo = repoFactory.create('cat');
  }

  async ngOnInit() {
    // 并行预加载所有需要的物种数据
    await Promise.all([
      this.humanRepo.load(),
      this.dogRepo.load(),
      this.catRepo.load()
    ]);
    // 数据加载完成,继续地牢生成逻辑
  }
}

房间组件中直接使用预加载好的仓库实例(可通过服务共享或输入传递):

@Component({ /* 组件配置 */ })
export class RoomComponent {
  @Input() speciesRepos: SpeciesRepository[] = [];

  getRandomSpeciesName(repo: SpeciesRepository): string | undefined {
    const dataLength = repo.getByIndex(0) ? repo.getByIndex(0).length : 0;
    return repo.getByIndex(Math.floor(Math.random() * dataLength));
  }
}

方案二:单例仓库维护多物种数据

如果不想管理多个实例,可以改造单例仓库,用Map存储不同物种的数据,实现单例下的多物种隔离:

@Injectable({ providedIn: 'root' })
export class SpeciesRepository {
  private speciesData = new Map<string, string[]>();
  private loadPromises = new Map<string, Promise<void>>();

  constructor(private httpClient: HttpClient) {}

  async load(speciesType: string): Promise<void> {
    if (this.loadPromises.has(speciesType)) {
      return this.loadPromises.get(speciesType)!;
    }
    if (this.speciesData.has(speciesType)) {
      return;
    }

    const promise = this.httpClient.get<string[]>(`assets/${speciesType}.json`)
      .toPromise()
      .then(data => {
        this.speciesData.set(speciesType, data);
        this.loadPromises.delete(speciesType);
      })
      .catch(err => {
        this.loadPromises.delete(speciesType);
        throw err;
      });

    this.loadPromises.set(speciesType, promise);
    return promise;
  }

  getByIndex(speciesType: string, index: number): string | undefined {
    return this.speciesData.get(speciesType)?.[index];
  }
}

使用时只需在调用方法时指定物种类型:

// 地牢生成组件预加载
await Promise.all([
  this.speciesRepo.load('human'),
  this.speciesRepo.load('dog')
]);

// 房间组件获取数据
const randomDogName = this.speciesRepo.getByIndex('dog', Math.floor(Math.random() * 2));

方案选择建议

  • 工厂模式:每个物种有独立的仓库实例,代码语义更清晰,适合需要将仓库实例传递给不同服务/组件的场景(贴合你的DDD分层思路)。
  • 单例多物种模式:实现更轻量,无需管理多个实例,适合仅需在调用时指定物种的简单场景。

两种方案都能避免新增物种时编写新类的问题,后续只需添加JSON文件,在业务逻辑中指定对应的物种类型即可。

内容的提问来源于stack exchange,提问作者James Morgan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 20:43:12