diff --git a/projects/natural/src/lib/classes/abstract-list.ts b/projects/natural/src/lib/classes/abstract-list.ts index 757032c9..1dbb7eb1 100644 --- a/projects/natural/src/lib/classes/abstract-list.ts +++ b/projects/natural/src/lib/classes/abstract-list.ts @@ -569,7 +569,7 @@ export class NaturalAbstractList< } /** - * Delete multiple items at once + * Delete multiple items at once, and then refresh the list of items automatically */ protected bulkDelete(): Observable { const subject = new Subject(); @@ -581,12 +581,17 @@ export class NaturalAbstractList< // never call this method. const selection = this.selection.selected as {id: string}[]; - this.service.delete(selection).subscribe(() => { - this.selection.clear(); - this.alertService.info($localize`Supprimé`); - subject.next(); - subject.complete(); - }); + this.service + .delete(selection, { + refetchQueries: this.service.allQuery ? [this.service.allQuery] : [], + awaitRefetchQueries: true, + }) + .subscribe(() => { + this.selection.clear(); + this.alertService.info($localize`Supprimé`); + subject.next(); + subject.complete(); + }); } }); diff --git a/projects/natural/src/lib/services/abstract-model.service.spec.ts b/projects/natural/src/lib/services/abstract-model.service.spec.ts index ef26162a..f822beb5 100644 --- a/projects/natural/src/lib/services/abstract-model.service.spec.ts +++ b/projects/natural/src/lib/services/abstract-model.service.spec.ts @@ -8,7 +8,6 @@ import {type Literal} from '../types/types'; import {NullService} from '../testing/null.service'; import {Apollo} from 'apollo-angular'; import {takeWhile} from 'rxjs/operators'; -import {type ObservableQuery} from '@apollo/client'; const observableError = 'Cannot use Observable as variables. Instead you should use .subscribe() to call the method with a real value'; @@ -27,41 +26,6 @@ describe('NaturalAbstractModelService', () => { service = TestBed.inject(PostService); }); - it('should be delay deleted resolving', fakeAsync(() => { - const apollo = TestBed.inject(Apollo); - - let resolveMyPromise: (value: ObservableQuery.Result[]) => void; - apollo.client.refetchObservableQueries = () => - new Promise[]>(resolve => { - resolveMyPromise = resolve; - }); - - let resolved = false; - let completed = false; - service.delete([{id: '123'}]).subscribe({ - next: () => { - resolved = true; - }, - complete: () => { - completed = true; - }, - }); - - expect(resolved).toBeFalse(); - expect(completed).toBeFalse(); - - tick(2000); - - expect(resolved).toBeFalse(); - expect(completed).toBeFalse(); - - resolveMyPromise!([]); - tick(2000); - - expect(resolved).toBeTrue(); - expect(completed).toBeTrue(); - })); - it('should be created', () => { expect(service).toBeTruthy(); }); @@ -233,7 +197,7 @@ describe('NaturalAbstractModelService', () => { }; let creationResult: any; - const creation = service.createOrUpdate(input, true); + const creation = service.createOrUpdate(input); creation.subscribe(v => (creationResult = v)); // After create, should be usual object after creation @@ -243,11 +207,11 @@ describe('NaturalAbstractModelService', () => { // Create or update again let updateResult: any; - const update = service.createOrUpdate(creationResult, true); + const update = service.createOrUpdate(creationResult); update.subscribe(v => (updateResult = v)); // should show created + updated objects merged - tick(); + tick(5000); expect('updateDate' in updateResult).toBeTrue(); flush(); @@ -263,11 +227,11 @@ describe('NaturalAbstractModelService', () => { let repeatedResult: any = null; // Create, should be cached - const creation = service.createOrUpdate(input, true); + const creation = service.createOrUpdate(input); creation.subscribe(res => (result = res)); // Repeated create should wait for the first creation, then update the object - const repeatedCreation = service.createOrUpdate(input, true); + const repeatedCreation = service.createOrUpdate(input); repeatedCreation.subscribe(res => (repeatedResult = res)); tick(5000); diff --git a/projects/natural/src/lib/services/abstract-model.service.ts b/projects/natural/src/lib/services/abstract-model.service.ts index 48f38963..7f377373 100644 --- a/projects/natural/src/lib/services/abstract-model.service.ts +++ b/projects/natural/src/lib/services/abstract-model.service.ts @@ -1,5 +1,11 @@ import {Apollo, gql, onlyCompleteData} from 'apollo-angular'; -import {type ApolloLink, NetworkStatus, type ObservableQuery, type WatchQueryFetchPolicy} from '@apollo/client'; +import { + type ApolloLink, + NetworkStatus, + type ObservableQuery, + type OperationVariables, + type WatchQueryFetchPolicy, +} from '@apollo/client'; import { type AbstractControl, type AsyncValidatorFn, @@ -10,7 +16,7 @@ import { import {type DocumentNode} from 'graphql'; import {merge, pick} from 'es-toolkit'; import {defaults} from 'es-toolkit/compat'; -import {catchError, combineLatest, EMPTY, first, from, Observable, of, type OperatorFunction} from 'rxjs'; +import {catchError, combineLatest, EMPTY, first, Observable, of, type OperatorFunction} from 'rxjs'; import {debounceTime, filter, map, shareReplay, startWith, switchMap, takeWhile, tap} from 'rxjs/operators'; import {NaturalQueryVariablesManager, type QueryVariables} from '../classes/query-variable-manager'; import {type Literal} from '../types/types'; @@ -32,6 +38,11 @@ export type FormControls = Record; export type WithId = {id: string} & T; +export type MutateOptionsWithoutVariables = Omit< + Apollo.MutateOptions, + 'mutation' | 'variables' +>; + export abstract class NaturalAbstractModelService< Tone, Vone extends {id: string}, @@ -68,7 +79,7 @@ export abstract class NaturalAbstractModelService< public constructor( protected readonly name: string, protected readonly oneQuery: DocumentNode | null, - protected readonly allQuery: DocumentNode | null, + public readonly allQuery: DocumentNode | null, protected readonly createMutation: DocumentNode | null, protected readonly updateMutation: DocumentNode | null, protected readonly deleteMutation: DocumentNode | null, @@ -313,13 +324,10 @@ export abstract class NaturalAbstractModelService< * This functions allow to quickly create or update objects. * * Manages a "creation is pending" status, and update when creation is ready. - * Uses regular update/updateNow and create methods. - * Used mainly when editing multiple objects in same controller (like in editable arrays) + * Uses regular `create()` (with immediate effect) and `update()` (with debounced effect) methods. + * Used mainly when editing multiple objects in the same controller, like in editable arrays. */ - public createOrUpdate( - object: Vcreate['input'] | WithId, - now = false, - ): Observable { + public createOrUpdate(object: Vcreate['input'] | WithId): Observable { this.throwIfObservable(object); this.throwIfNotQuery(this.createMutation); this.throwIfNotQuery(this.updateMutation); @@ -339,12 +347,7 @@ export abstract class NaturalAbstractModelService< // If object has Id, just save it if ('id' in object && object.id) { - if (now) { - // used mainly for tests, because lodash debounced used in update() does not work fine with fakeAsync and tick() - return this.updateNow(object as WithId); - } else { - return this.update(object as WithId); - } + return this.update(object as WithId); } // If object was not saving, and has no ID, create it @@ -362,9 +365,12 @@ export abstract class NaturalAbstractModelService< } /** - * Create an object in DB and then refetch the list of objects + * Create an object in DB */ - public create(object: Vcreate['input']): Observable { + public create( + object: Vcreate['input'], + options: MutateOptionsWithoutVariables = {}, + ): Observable { this.throwIfObservable(object); this.throwIfNotQuery(this.createMutation); @@ -375,15 +381,11 @@ export abstract class NaturalAbstractModelService< return this.apollo .mutate({ + ...(options as MutateOptionsWithoutVariables), mutation: this.createMutation, variables: variables, }) - .pipe( - map(result => { - this.apollo.client.refetchObservableQueries(); - return this.mapCreation(result); - }), - ); + .pipe(map(result => this.mapCreation(result))); } /** @@ -402,7 +404,10 @@ export abstract class NaturalAbstractModelService< /** * Update an object immediately when subscribing */ - public updateNow(object: WithId): Observable { + public updateNow( + object: WithId, + options: MutateOptionsWithoutVariables = {}, + ): Observable { this.throwIfObservable(object); this.throwIfNotQuery(this.updateMutation); @@ -416,21 +421,20 @@ export abstract class NaturalAbstractModelService< return this.apollo .mutate({ + ...(options as MutateOptionsWithoutVariables), mutation: this.updateMutation, variables: variables, }) - .pipe( - map(result => { - this.apollo.client.refetchObservableQueries(); - return this.mapUpdate(result); - }), - ); + .pipe(map(result => this.mapUpdate(result))); } /** - * Delete objects and then refetch the list of objects + * Delete objects */ - public delete(objects: {id: string}[]): Observable { + public delete( + objects: {id: string}[], + options: MutateOptionsWithoutVariables = {}, + ): Observable { this.throwIfObservable(objects); this.throwIfNotQuery(this.deleteMutation); @@ -449,17 +453,11 @@ export abstract class NaturalAbstractModelService< return this.apollo .mutate({ + ...(options as MutateOptionsWithoutVariables), mutation: this.deleteMutation, variables: variables, }) - .pipe( - // Delay the observable until Apollo refetch is completed - switchMap(result => { - const mappedResult = this.mapDelete(result); - - return from(this.apollo.client.refetchObservableQueries()).pipe(map(() => mappedResult)); - }), - ); + .pipe(map(result => this.mapDelete(result))); } /**