diff --git a/src/Bones.UI/core/composableFactory.ts b/src/Bones.UI/core/composableFactory.ts index b2d9aa8..879632d 100644 --- a/src/Bones.UI/core/composableFactory.ts +++ b/src/Bones.UI/core/composableFactory.ts @@ -9,7 +9,7 @@ export class ComposableFactory { return ComposableFactory.customGet(service, service.get, applyFactory); } - public static getMany(service: { getMany(filter?: TFilter): Promise } & INotifyService, applyFactory?: () => (entities: Ref) => void) { + public static getMany(service: { getMany(filter?: TFilter, controller?: AbortController): Promise } & INotifyService, applyFactory?: () => (entities: Ref) => void) { return ComposableFactory.customGetMany(service, service.getMany, applyFactory); } @@ -115,9 +115,11 @@ export class ComposableFactory { * Warning : read the code before using this method, the first argument in the method is used to create a filter * The last argument passed to the getMany composable can be a custom filter function that will override the default filter * */ - public static customGetMany(service: INotifyService, method: (...args: TArgs) => Promise, applyFactory?: () => (entities: Ref) => void) { + public static customGetMany(service: INotifyService, method: (...args: [...TArgs, AbortController]) => Promise, applyFactory?: () => (entities: Ref) => void) { return () => { const apply = applyFactory ? applyFactory() : () => { }; + + let cancellationToken = new AbortController(); let subscribersIds: number[] = []; onUnmounted(() => { @@ -129,6 +131,11 @@ export class ComposableFactory { const entities = ref([]) as Ref; const getMany = async (...args: [...TArgs, ((el: TDetails) => boolean)?]) => { + + // abort previous request if any + cancellationToken.abort(); + cancellationToken = new AbortController(); + fetching.value = true; let customFilter: ((el: TDetails) => boolean) | undefined = undefined @@ -140,7 +147,7 @@ export class ComposableFactory { let actualArgs = args as unknown as TArgs; try { - entities.value = await method(...actualArgs); + entities.value = await method(...actualArgs, cancellationToken); if (apply) apply(entities) } finally { @@ -153,7 +160,7 @@ export class ComposableFactory { subscribersIds.push(service.subscribe("reset", async () => { fetching.value = true; try { - entities.value = await method(...actualArgs); + entities.value = await method(...actualArgs, cancellationToken); if (apply) apply(entities) } finally { diff --git a/src/Bones.UI/core/serviceFactory.ts b/src/Bones.UI/core/serviceFactory.ts index 3c8c680..7993231 100644 --- a/src/Bones.UI/core/serviceFactory.ts +++ b/src/Bones.UI/core/serviceFactory.ts @@ -36,9 +36,9 @@ export class ServiceFactory { } addGetMany(url: string | (() => string), entity: new (dto: TInfosDTO) => TInfos) - : { getMany: (filter?: TFilter) => Promise } { + : { getMany: (filter?: TFilter, controller?: AbortController) => Promise } { - const getMany = async (filter?: TFilter) => { + const getMany = async (filter?: TFilter, controller?: AbortController) => { const realUrl = typeof url === "string" ? url : url(); let response; @@ -46,10 +46,14 @@ export class ServiceFactory { // If the service is configured to use GET as POST to prevent issues with large query strings, // we send the filter as a POST request with a "_method" parameter to indicate it's a GET request. if (ServiceFactory.getAsPost && filter) { - response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter); + response = await ServiceFactory.http.post(buildURL(realUrl, { "_method": "GET" }), filter, { + signal: controller?.signal + }); } else { - response = await ServiceFactory.http.get(buildURL(realUrl, filter)); + response = await ServiceFactory.http.get(buildURL(realUrl, filter), { + signal: controller?.signal + }); } const dtos: TInfosDTO[] = response.data; @@ -76,7 +80,7 @@ export class ServiceFactory { return { get }; } - static addCustom(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise, mapper: (dto: TResultDTO) => TResult): Record Promise> { + static addCustom(name: T, call: (axios: AxiosInstance, ...args: TArgs) => Promise, mapper: (dto: TResultDTO) => TResult) { const fetch = async (...args: TArgs) => { const response = await call(ServiceFactory.http, ...args); @@ -90,6 +94,21 @@ export class ServiceFactory { return { [name]: fetch } as Record Promise>; } + + static addCustomCancellable(name: T, call: (axios: AxiosInstance, ...args: [...TArgs, AbortController]) => Promise, mapper: (dto: TResultDTO) => TResult) { + + const fetch = async (...args: [...TArgs, AbortController]) => { + const response = await call(ServiceFactory.http, ...args); + const dto: TResultDTO = response.data; + + const result = mapper(dto); + + return result; + } + + return { [name]: fetch } as Record Promise>; + } + addCreate(url: string | (() => string)) : { create: (dto: TCreateDTO) => Promise } { diff --git a/tests/Bones.UI.Tests/services/testUserService.ts b/tests/Bones.UI.Tests/services/testUserService.ts index 2bb3b96..c1c3cec 100644 --- a/tests/Bones.UI.Tests/services/testUserService.ts +++ b/tests/Bones.UI.Tests/services/testUserService.ts @@ -17,7 +17,7 @@ const AccountLoginFactory = new ServiceFactory axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("current", axios => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), ServiceFactory.addCustom("complexCurrent", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new TestUserDetails(dto)), - ServiceFactory.addCustom("complexGetMany", (axios, p1: string, p2: number) => axios.get(TEST_USERS_URL), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), + ServiceFactory.addCustomCancellable("complexGetMany", (axios, p1: string, p2: number, controller: AbortController) => axios.get(TEST_USERS_URL, { signal: controller.signal }), (dto: TestUserDetailsDTO) => new Array(5).map(a => new TestUserDetails(dto))), )); export const useTestUsersSync = ComposableFactory.sync(testUserServiceFactory);