Skip to content

Commit

Permalink
Fix circular dependency issues
Browse files Browse the repository at this point in the history
  • Loading branch information
hudson-newey committed Nov 7, 2024
1 parent f89408c commit 2a827ec
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 131 deletions.
7 changes: 7 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 } },
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Injector> {
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,
Expand All @@ -44,7 +26,9 @@ export class AssociationInjectorService {
provide: BAW_SERVICE_OPTIONS,
useValue: { disableNotification: true },
},
...providedServices,
...services,
...serviceTokens,
...serviceResolvers
],
});
}
Expand Down
134 changes: 38 additions & 96 deletions src/app/services/baw-api/ServiceTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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
Expand All @@ -115,99 +116,39 @@ export class ServiceToken<
}

export const ACCOUNT = new ServiceToken<AccountsService, User>("ACCOUNT");
export const ANALYSIS_JOB = new ServiceToken<AnalysisJobsService, AnalysisJob>(
"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<AudioEventsService, AudioEvent>(
"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<ContactUsService, ContactUs>(
"CONTACT_US"
);
export const BOOKMARK = new ServiceToken<BookmarksService, Bookmark>(
"BOOKMARK"
);
export const ANALYSIS_JOB = new ServiceToken<AnalysisJobsService, AnalysisJob>("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<AudioEventsService, AudioEvent>("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<ContactUsService, ContactUs>("CONTACT_US");
export const BOOKMARK = new ServiceToken<BookmarksService, Bookmark>("BOOKMARK");
export const DATASET = new ServiceToken<DatasetsService, Dataset>("DATASET");
export const DATASET_ITEM = new ServiceToken<DatasetItemsService, DatasetItem>(
"D_ITEM"
);
export const DATA_REQUEST = new ServiceToken<DataRequestService, DataRequest>(
"DATA_REQUEST"
);
export const DATASET_ITEM = new ServiceToken<DatasetItemsService, DatasetItem>("D_ITEM");
export const DATA_REQUEST = new ServiceToken<DataRequestService, DataRequest>("DATA_REQUEST");
export const HARVEST = new ServiceToken<HarvestsService, Harvest>("HARVEST");
export const SHALLOW_HARVEST = new ServiceToken<
ShallowHarvestsService,
Harvest
>("SHALLOW_HARVEST");
export const HARVEST_ITEM = new ServiceToken<HarvestItemsService, HarvestItem>(
"HARVEST_ITEM"
);
export const SHALLOW_HARVEST_ITEM = new ServiceToken<
ShallowHarvestItemsService,
HarvestItem
>("SHALLOW_HARVEST_ITEM");
export const PERMISSION = new ServiceToken<PermissionsService, Permission>(
"PROGRESS"
);
export const PROGRESS_EVENT = new ServiceToken<
ProgressEventsService,
ProgressEvent
>("PROGRESS");
export const SHALLOW_HARVEST = new ServiceToken<ShallowHarvestsService, Harvest>("SHALLOW_HARVEST");
export const HARVEST_ITEM = new ServiceToken<HarvestItemsService, HarvestItem>("HARVEST_ITEM");
export const SHALLOW_HARVEST_ITEM = new ServiceToken<ShallowHarvestItemsService, HarvestItem>("SHALLOW_HARVEST_ITEM");
export const PERMISSION = new ServiceToken<PermissionsService, Permission>("PROGRESS");
export const PROGRESS_EVENT = new ServiceToken<ProgressEventsService, ProgressEvent>("PROGRESS");
export const PROJECT = new ServiceToken<ProjectsService, Project>("PROJECT");
export const QUESTION = new ServiceToken<QuestionsService, Question>(
"QUESTION"
);
export const SHALLOW_QUESTION = new ServiceToken<
ShallowQuestionsService,
Question
>("S_QUESTION");
export const QUESTION = new ServiceToken<QuestionsService, Question>("QUESTION");
export const SHALLOW_QUESTION = new ServiceToken<ShallowQuestionsService, Question>("S_QUESTION");
export const REGION = new ServiceToken<RegionsService, Region>("REGION");
export const SHALLOW_REGION = new ServiceToken<ShallowRegionsService, Region>(
"S_REGION"
);
export const REPORT_PROBLEM = new ServiceToken<
ReportProblemService,
ReportProblem
>("REPORT_PROBLEM");
export const RESPONSE = new ServiceToken<ResponsesService, Response>(
"RESPONSE"
);
export const SHALLOW_RESPONSE = new ServiceToken<
ShallowResponsesService,
Response
>("S_RESPONSE");
export const SAVED_SEARCH = new ServiceToken<SavedSearchesService, SavedSearch>(
"SAVED_SEARCH"
);
export const SHALLOW_REGION = new ServiceToken<ShallowRegionsService, Region>("S_REGION");
export const REPORT_PROBLEM = new ServiceToken<ReportProblemService, ReportProblem>("REPORT_PROBLEM");
export const RESPONSE = new ServiceToken<ResponsesService, Response>("RESPONSE");
export const SHALLOW_RESPONSE = new ServiceToken<ShallowResponsesService, Response>("S_RESPONSE");
export const SAVED_SEARCH = new ServiceToken<SavedSearchesService, SavedSearch>("SAVED_SEARCH");
export const SCRIPT = new ServiceToken<ScriptsService, Script>("SCRIPT");
export const SHALLOW_SITE = new ServiceToken<ShallowSitesService, Site>(
"S_SITE"
);
export const SHALLOW_SITE = new ServiceToken<ShallowSitesService, Site>("S_SITE");
export const SITE = new ServiceToken<SitesService, Site>("SITE");
export const STATISTICS = new ServiceToken<StatisticsService, Statistics>(
"STATISTICS"
);
export const STATISTICS = new ServiceToken<StatisticsService, Statistics>("STATISTICS");
export const STUDY = new ServiceToken<StudiesService, Study>("STUDY");
export const TAG = new ServiceToken<TagsService, Tag>("TAG");
export const TAG_GROUP = new ServiceToken<TagGroupsService, TagGroup>(
"TAG_GROUP"
);
export const TAG_GROUP = new ServiceToken<TagGroupsService, TagGroup>("TAG_GROUP");
export const TAGGING = new ServiceToken<TaggingsService, Tagging>("TAGGING");
export const USER = new ServiceToken<UserService, User>("USER");
export const AUDIO_EVENT_PROVENANCE = new ServiceToken< AudioEventProvenanceService, AudioEventProvenance >("AUDIO_EVENT_PROVENANCE");
Expand All @@ -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<AnnotationService, Annotation>( "ANNOTATION");
export const MEDIA = new ServiceToken<MediaService, never>("MEDIA");
export const ASSOCIATION_INJECTOR = new ServiceToken<AssociationInjectorService, never>("ASSOCIATION_INJECTOR");
4 changes: 2 additions & 2 deletions src/app/services/baw-api/baw-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 => {
Expand Down

0 comments on commit 2a827ec

Please sign in to comment.