Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions projects/natural/src/lib/classes/abstract-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const subject = new Subject<void>();
Expand All @@ -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(() => {
Comment thread
PowerKiKi marked this conversation as resolved.
this.selection.clear();
this.alertService.info($localize`Supprimé`);
subject.next();
subject.complete();
});
}
});

Expand Down
46 changes: 5 additions & 41 deletions projects/natural/src/lib/services/abstract-model.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<any>[]) => void;
apollo.client.refetchObservableQueries = () =>
new Promise<ObservableQuery.Result<any>[]>(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();
});
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand All @@ -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);
Expand Down
78 changes: 38 additions & 40 deletions projects/natural/src/lib/services/abstract-model.service.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand All @@ -32,6 +38,11 @@ export type FormControls = Record<string, AbstractControl>;

export type WithId<T> = {id: string} & T;

export type MutateOptionsWithoutVariables<Tcreate, Vcreate extends OperationVariables> = Omit<
Apollo.MutateOptions<Tcreate, Vcreate>,
'mutation' | 'variables'
>;

export abstract class NaturalAbstractModelService<
Tone,
Vone extends {id: string},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Vupdate['input']>,
now = false,
): Observable<Tcreate | Tupdate> {
public createOrUpdate(object: Vcreate['input'] | WithId<Vupdate['input']>): Observable<Tcreate | Tupdate> {
this.throwIfObservable(object);
this.throwIfNotQuery(this.createMutation);
this.throwIfNotQuery(this.updateMutation);
Expand All @@ -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<Vupdate['input']>);
} else {
return this.update(object as WithId<Vupdate['input']>);
}
return this.update(object as WithId<Vupdate['input']>);
}

// If object was not saving, and has no ID, create it
Expand All @@ -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<Tcreate> {
public create(
object: Vcreate['input'],
options: MutateOptionsWithoutVariables<Literal, Literal> = {},
): Observable<Tcreate> {
Comment thread
PowerKiKi marked this conversation as resolved.
this.throwIfObservable(object);
this.throwIfNotQuery(this.createMutation);

Expand All @@ -375,15 +381,11 @@ export abstract class NaturalAbstractModelService<

return this.apollo
.mutate<Tcreate, Vcreate>({
...(options as MutateOptionsWithoutVariables<Tcreate, Vcreate>),
mutation: this.createMutation,
variables: variables,
})
.pipe(
map(result => {
this.apollo.client.refetchObservableQueries();
return this.mapCreation(result);
}),
);
.pipe(map(result => this.mapCreation(result)));
Comment thread
PowerKiKi marked this conversation as resolved.
}

/**
Expand All @@ -402,7 +404,10 @@ export abstract class NaturalAbstractModelService<
/**
* Update an object immediately when subscribing
*/
public updateNow(object: WithId<Vupdate['input']>): Observable<Tupdate> {
public updateNow(
object: WithId<Vupdate['input']>,
options: MutateOptionsWithoutVariables<Literal, Literal> = {},
): Observable<Tupdate> {
this.throwIfObservable(object);
this.throwIfNotQuery(this.updateMutation);

Expand All @@ -416,21 +421,20 @@ export abstract class NaturalAbstractModelService<

return this.apollo
.mutate<Tupdate, Vupdate>({
...(options as MutateOptionsWithoutVariables<Tupdate, Vupdate>),
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<Tdelete> {
public delete(
objects: {id: string}[],
options: MutateOptionsWithoutVariables<Literal, Literal> = {},
): Observable<Tdelete> {
this.throwIfObservable(objects);
this.throwIfNotQuery(this.deleteMutation);

Expand All @@ -449,17 +453,11 @@ export abstract class NaturalAbstractModelService<

return this.apollo
.mutate<Tdelete, Vdelete>({
...(options as MutateOptionsWithoutVariables<Tdelete, Vdelete>),
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)));
}

/**
Expand Down
Loading