From 2a827eccb6590b8920d054aed0706c8545af616a Mon Sep 17 00:00:00 2001 From: hudson-newey Date: Thu, 7 Nov 2024 11:28:20 +1000 Subject: [PATCH] Fix circular dependency issues --- .prettierignore | 7 + src/app/app.module.ts | 3 + .../association-injector.service.spec.ts | 11 +- .../association-injector.service.ts | 44 ++---- src/app/services/baw-api/ServiceTokens.ts | 134 +++++------------- src/app/services/baw-api/baw-api.service.ts | 4 +- 6 files changed, 72 insertions(+), 131 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..000b7882e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +#! this file should only be used as a last resort to exclude files from pettier +# you should always prefer to use // prettier-ignore to exluce individual lines + +# we ignore the service token file from pettier so that we can one-line all of +# the service tokens +# this makes it much more readable as each service token has its ownn line +src/app/services/baw-api/ServiceTokens.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index a57e20a5c..99bedd821 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -30,6 +30,8 @@ import { TitleStrategy } from "@angular/router"; import { AnnotationsImportModule } from "@components/import-annotations/import-annotations.module"; import { WebsiteStatusModule } from "@components/website-status/website-status.module"; import { AnnotationModule } from "@components/annotations/annotation.module"; +import { ASSOCIATION_INJECTOR } from "@baw-api/ServiceTokens"; +import { AssociationInjectorService } from "@services/association-injector/association-injector.service"; import { AppRoutingModule } from "./app-routing.module"; import { AppComponent, PageTitleStrategy } from "./app.component"; import { toastrRoot } from "./app.helper"; @@ -107,6 +109,7 @@ export const appImports = [ ...appImports, ], providers: [ + { provide: ASSOCIATION_INJECTOR.token, useClass: AssociationInjectorService }, { provide: TitleStrategy, useClass: PageTitleStrategy }, // Show loading animation after 3 seconds { provide: LOADING_BAR_CONFIG, useValue: { latencyThreshold: 200 } }, diff --git a/src/app/services/association-injector/association-injector.service.spec.ts b/src/app/services/association-injector/association-injector.service.spec.ts index 84f6559bd..28453faef 100644 --- a/src/app/services/association-injector/association-injector.service.spec.ts +++ b/src/app/services/association-injector/association-injector.service.spec.ts @@ -1,6 +1,6 @@ import { createServiceFactory, SpectatorService } from "@ngneat/spectator"; import { BAW_SERVICE_OPTIONS } from "@baw-api/api-common"; -import { ACCOUNT } from "@baw-api/ServiceTokens"; +import { ACCOUNT, ASSOCIATION_INJECTOR } from "@baw-api/ServiceTokens"; import { MockBawApiModule } from "@baw-api/baw-apiMock.module"; import { ToastrService } from "ngx-toastr"; import { GlobalsService } from "@services/globals/globals.service"; @@ -14,15 +14,20 @@ describe("AssociationInjectorService", () => { service: AssociationInjectorService, imports: [MockBawApiModule], mocks: [ToastrService], + providers: [ + { + provide: ASSOCIATION_INJECTOR.token, + useExisting: AssociationInjectorService, + }, + ], }); function associationInjector(): Injector { return spectator.service.instance; } - beforeEach(async () => { + beforeEach(() => { spectator = createService(); - spectator.service.instance = await spectator.service.createInstance(); }); it("should create", () => { diff --git a/src/app/services/association-injector/association-injector.service.ts b/src/app/services/association-injector/association-injector.service.ts index 0b2d68886..13b060baa 100644 --- a/src/app/services/association-injector/association-injector.service.ts +++ b/src/app/services/association-injector/association-injector.service.ts @@ -1,40 +1,22 @@ import { Injectable, Injector } from "@angular/core"; import { BAW_SERVICE_OPTIONS } from "../baw-api/api-common"; -// import { -// ShallowSitesService, -// shallowSiteResolvers, -// } from "../baw-api/site/sites.service"; import { BawApiService } from "../baw-api/baw-api.service"; -// import * as Tokens from "../baw-api/ServiceTokens"; -// import { serviceList } from "../baw-api/ServiceProviders"; +import { services, serviceTokens, serviceResolvers } from "../baw-api/ServiceProviders"; -// const serviceList = [ -// { -// serviceToken: Tokens.SHALLOW_SITE, -// service: ShallowSitesService, -// resolvers: shallowSiteResolvers, -// }, -// ]; - -@Injectable({ providedIn: "root" }) +//! WARNING: to prevent a circular dependency issue, this service should be imported +// through its "ASSOCIATION_INJECTOR" token +@Injectable() export class AssociationInjectorService { - public constructor(private injector: Injector) { - // TODO: fix this potential race condition - this.createInstance().then((instance) => { - this.instance = instance; - }); - } + public constructor(private injector: Injector) {} - public instance?: Injector; - - public async createInstance(): Promise { - const imported = await import("../baw-api/ServiceProviders"); - const serviceList = imported.serviceList; + public get instance(): Injector { + this._instance ??= this.createInstance(); + return this._instance; + } - const providedServices = serviceList.map(({ service, serviceToken }) => { - return { provide: serviceToken.token, useClass: service }; - }); + private _instance?: Injector; + private createInstance(): Injector { return Injector.create({ name: "AssociationInjector", parent: this.injector, @@ -44,7 +26,9 @@ export class AssociationInjectorService { provide: BAW_SERVICE_OPTIONS, useValue: { disableNotification: true }, }, - ...providedServices, + ...services, + ...serviceTokens, + ...serviceResolvers ], }); } diff --git a/src/app/services/baw-api/ServiceTokens.ts b/src/app/services/baw-api/ServiceTokens.ts index 4ce63f757..91b80ee1b 100644 --- a/src/app/services/baw-api/ServiceTokens.ts +++ b/src/app/services/baw-api/ServiceTokens.ts @@ -35,13 +35,14 @@ import type { Tag } from "@models/Tag"; import type { Tagging } from "@models/Tagging"; import type { TagGroup } from "@models/TagGroup"; import type { User } from "@models/User"; -import { AudioEventProvenance } from "@models/AudioEventProvenance"; -import { EventSummaryReport } from "@models/EventSummaryReport"; -import { AudioEventImport } from "@models/AudioEventImport"; -import { WebsiteStatus } from "@models/WebsiteStatus"; -import { Annotation } from "@models/data/Annotation"; -import { AnnotationService } from "@services/models/annotation.service"; -import { MediaService } from "@services/media/media.service"; +import type { AudioEventProvenance } from "@models/AudioEventProvenance"; +import type { EventSummaryReport } from "@models/EventSummaryReport"; +import type { AudioEventImport } from "@models/AudioEventImport"; +import type { WebsiteStatus } from "@models/WebsiteStatus"; +import type { Annotation } from "@models/data/Annotation"; +import type { AnnotationService } from "@services/models/annotation.service"; +import type { MediaService } from "@services/media/media.service"; +import type { AssociationInjectorService } from "@services/association-injector/association-injector.service"; import { AccountsService } from "./account/accounts.service"; import type { AnalysisJobItemsService } from "./analysis/analysis-job-items.service"; import type { AnalysisJobsService } from "./analysis/analysis-jobs.service"; @@ -89,10 +90,10 @@ import type { TaggingsService } from "./tag/taggings.service"; import type { TagsService } from "./tag/tags.service"; import type { UserService } from "./user/user.service"; import type { AnalysisJobItemResultsService } from "./analysis/analysis-job-item-result.service"; -import { AudioEventProvenanceService } from "./AudioEventProvenance/AudioEventProvenance.service"; -import { EventSummaryReportService } from "./reports/event-report/event-summary-report.service"; -import { AudioEventImportService } from "./audio-event-import/audio-event-import.service"; -import { WebsiteStatusService } from "./website-status/website-status.service"; +import type { AudioEventProvenanceService } from "./AudioEventProvenance/AudioEventProvenance.service"; +import type { EventSummaryReportService } from "./reports/event-report/event-summary-report.service"; +import type { AudioEventImportService } from "./audio-event-import/audio-event-import.service"; +import type { WebsiteStatusService } from "./website-status/website-status.service"; /** * Wrapper for InjectionToken class. This is required because of @@ -115,99 +116,39 @@ export class ServiceToken< } export const ACCOUNT = new ServiceToken("ACCOUNT"); -export const ANALYSIS_JOB = new ServiceToken( - "A_JOB" -); -export const ANALYSIS_JOB_ITEM = new ServiceToken< - AnalysisJobItemsService, - AnalysisJobItem ->("A_JOB_ITEM"); -export const ANALYSIS_JOB_ITEM_RESULTS = new ServiceToken< - AnalysisJobItemResultsService, - AnalysisJobItemResult ->("A_JOB_ITEM_RESULTS"); -export const AUDIO_EVENT = new ServiceToken( - "AUDIO" -); -export const SHALLOW_AUDIO_EVENT = new ServiceToken< - ShallowAudioEventsService, - AudioEvent ->("S_AUDIO"); -export const AUDIO_RECORDING = new ServiceToken< - AudioRecordingsService, - AudioRecording ->("RECORDING"); -export const CONTACT_US = new ServiceToken( - "CONTACT_US" -); -export const BOOKMARK = new ServiceToken( - "BOOKMARK" -); +export const ANALYSIS_JOB = new ServiceToken("A_JOB"); +export const ANALYSIS_JOB_ITEM = new ServiceToken("A_JOB_ITEM"); +export const ANALYSIS_JOB_ITEM_RESULTS = new ServiceToken("A_JOB_ITEM_RESULTS"); +export const AUDIO_EVENT = new ServiceToken("AUDIO"); +export const SHALLOW_AUDIO_EVENT = new ServiceToken("S_AUDIO"); +export const AUDIO_RECORDING = new ServiceToken("RECORDING"); +export const CONTACT_US = new ServiceToken("CONTACT_US"); +export const BOOKMARK = new ServiceToken("BOOKMARK"); export const DATASET = new ServiceToken("DATASET"); -export const DATASET_ITEM = new ServiceToken( - "D_ITEM" -); -export const DATA_REQUEST = new ServiceToken( - "DATA_REQUEST" -); +export const DATASET_ITEM = new ServiceToken("D_ITEM"); +export const DATA_REQUEST = new ServiceToken("DATA_REQUEST"); export const HARVEST = new ServiceToken("HARVEST"); -export const SHALLOW_HARVEST = new ServiceToken< - ShallowHarvestsService, - Harvest ->("SHALLOW_HARVEST"); -export const HARVEST_ITEM = new ServiceToken( - "HARVEST_ITEM" -); -export const SHALLOW_HARVEST_ITEM = new ServiceToken< - ShallowHarvestItemsService, - HarvestItem ->("SHALLOW_HARVEST_ITEM"); -export const PERMISSION = new ServiceToken( - "PROGRESS" -); -export const PROGRESS_EVENT = new ServiceToken< - ProgressEventsService, - ProgressEvent ->("PROGRESS"); +export const SHALLOW_HARVEST = new ServiceToken("SHALLOW_HARVEST"); +export const HARVEST_ITEM = new ServiceToken("HARVEST_ITEM"); +export const SHALLOW_HARVEST_ITEM = new ServiceToken("SHALLOW_HARVEST_ITEM"); +export const PERMISSION = new ServiceToken("PROGRESS"); +export const PROGRESS_EVENT = new ServiceToken("PROGRESS"); export const PROJECT = new ServiceToken("PROJECT"); -export const QUESTION = new ServiceToken( - "QUESTION" -); -export const SHALLOW_QUESTION = new ServiceToken< - ShallowQuestionsService, - Question ->("S_QUESTION"); +export const QUESTION = new ServiceToken("QUESTION"); +export const SHALLOW_QUESTION = new ServiceToken("S_QUESTION"); export const REGION = new ServiceToken("REGION"); -export const SHALLOW_REGION = new ServiceToken( - "S_REGION" -); -export const REPORT_PROBLEM = new ServiceToken< - ReportProblemService, - ReportProblem ->("REPORT_PROBLEM"); -export const RESPONSE = new ServiceToken( - "RESPONSE" -); -export const SHALLOW_RESPONSE = new ServiceToken< - ShallowResponsesService, - Response ->("S_RESPONSE"); -export const SAVED_SEARCH = new ServiceToken( - "SAVED_SEARCH" -); +export const SHALLOW_REGION = new ServiceToken("S_REGION"); +export const REPORT_PROBLEM = new ServiceToken("REPORT_PROBLEM"); +export const RESPONSE = new ServiceToken("RESPONSE"); +export const SHALLOW_RESPONSE = new ServiceToken("S_RESPONSE"); +export const SAVED_SEARCH = new ServiceToken("SAVED_SEARCH"); export const SCRIPT = new ServiceToken("SCRIPT"); -export const SHALLOW_SITE = new ServiceToken( - "S_SITE" -); +export const SHALLOW_SITE = new ServiceToken("S_SITE"); export const SITE = new ServiceToken("SITE"); -export const STATISTICS = new ServiceToken( - "STATISTICS" -); +export const STATISTICS = new ServiceToken("STATISTICS"); export const STUDY = new ServiceToken("STUDY"); export const TAG = new ServiceToken("TAG"); -export const TAG_GROUP = new ServiceToken( - "TAG_GROUP" -); +export const TAG_GROUP = new ServiceToken("TAG_GROUP"); export const TAGGING = new ServiceToken("TAGGING"); export const USER = new ServiceToken("USER"); export const AUDIO_EVENT_PROVENANCE = new ServiceToken< AudioEventProvenanceService, AudioEventProvenance >("AUDIO_EVENT_PROVENANCE"); @@ -216,3 +157,4 @@ export const AUDIO_EVENT_IMPORT = new ServiceToken< AudioEventImportService, Aud export const WEBSITE_STATUS = new ServiceToken< WebsiteStatusService, WebsiteStatus >("WEBSITE_STATUS"); export const ANNOTATION = new ServiceToken( "ANNOTATION"); export const MEDIA = new ServiceToken("MEDIA"); +export const ASSOCIATION_INJECTOR = new ServiceToken("ASSOCIATION_INJECTOR"); diff --git a/src/app/services/baw-api/baw-api.service.ts b/src/app/services/baw-api/baw-api.service.ts index 4c887ce47..2c26fcb36 100644 --- a/src/app/services/baw-api/baw-api.service.ts +++ b/src/app/services/baw-api/baw-api.service.ts @@ -21,10 +21,10 @@ import { Observable, iif, of, throwError } from "rxjs"; import { catchError, concatMap, map, switchMap, tap } from "rxjs/operators"; import { IS_SERVER_PLATFORM } from "src/app/app.helper"; import { ContextOptions } from "@ngneat/cashew/lib/cache-context"; -import { AssociationInjectorService } from "@services/association-injector/association-injector.service"; import { BawSessionService } from "./baw-session.service"; import { CREDENTIALS_CONTEXT } from "./api.interceptor.service"; import { BAW_SERVICE_OPTIONS } from "./api-common"; +import { ASSOCIATION_INJECTOR } from "./ServiceTokens"; export const defaultApiPageSize = 25; export const unknownErrorCode = -1; @@ -150,10 +150,10 @@ export class BawApiService< @Inject(IS_SERVER_PLATFORM) protected isServer: boolean, protected manager: HttpCacheManager, protected http: HttpClient, - protected associationInjector: AssociationInjectorService, protected session: BawSessionService, protected notifications: ToastrService, @Inject(CACHE_SETTINGS) private cacheSettings: CacheSettings, + @Inject(ASSOCIATION_INJECTOR.token) protected associationInjector: any, @Optional() @Inject(BAW_SERVICE_OPTIONS) private options: BawServiceOptions ) { const createModel = (cb: ClassBuilder, data: Model, meta: Meta): Model => {