Skip to content

Commit

Permalink
* User errReporter to report errors, and don't use console by def…
Browse files Browse the repository at this point in the history
…ault.

* `Comparator` class (and `comparatorFields` parameter) now understand composite fields.
* Update NgRx dependencies to v15 (stable).
  • Loading branch information
e-oz committed Nov 29, 2022
1 parent 609d783 commit e426e1a
Show file tree
Hide file tree
Showing 8 changed files with 240 additions and 32 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
### 1.2.8
* User `errReporter` to report errors, and don't use `console` by default.
* `Comparator` class (and `comparatorFields` parameter) now understand composite fields.
* Update NgRx dependencies to v15 (stable).

### 1.2.7
* Restore global configuration feature (`COLLECTION_SERVICE_OPTIONS` token)

Expand Down
44 changes: 40 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,36 @@ The equality of items will be checked using the comparator that you can replace

The comparator will compare items using `===` first, then it will use id fields.

You can check the id fields list of the default comparator in [comparator.ts](projects/ngx-collection/src/lib/comparator.ts) file.
You can check the default id fields list of the default comparator in [comparator.ts](projects/ngx-collection/src/lib/comparator.ts) file.

You can easily configure this using Angular DI:
```ts
{
provide: 'COLLECTION_SERVICE_OPTIONS',
useValue: {
comparatorFields: ['uuId', 'url'],
}
},
```

or by re-instantiating a Comparator:

```ts
export class NotificationsCollectionService extends CollectionService<Notification> {
override init() {
this.setComparator(new Comparator(['signature']))
}
}
```

or by providing your own Comparator - it can be a class, implementing `ObjectsComparator` interface, or a function, implementing `ObjectsComparatorFn`.

## Comparator fields

In default comparator, every item in the list of fields can be:
1. `string` - if both objects have this field, and values are equal, objects are equal.
If both objects have this field, and values are not equal - objects are not equal and __comparison stops__.
2. `string[]` - composite field: if both objects have every field enlisted, and every value is equal, objects are equal.

## Duplicates prevention

Expand All @@ -254,10 +283,10 @@ To find a duplicate, items will be compared by comparator - objects equality che

A collection might have duplicates because of some data error or because of wrong fields in the comparator - you can redefine them.

If a duplicate is detected, the item will not be added to the collection and an error will be printed to the console (if `window.console` does exist).
If a duplicate is detected, the item will not be added to the collection and an error will be sent to the `errReporter` (if `errReporter` is set).
You can call `setThrowOnDuplicates('some message')` to make Collection Service throw an exception with the message you expect.

Method `read()` by default will put returned items to the collection even if they have duplicates, but `console.error` will be raised (if `window.console` exists),
Method `read()` by default will put returned items to the collection even if they have duplicates, but `errReporter` will be called (if `errReporter` is set),
because in this case you have a chance to damage your data in future `update()`.
You can call `setAllowFetchedDuplicates(false)` to instruct `read()` to not accept items lists with duplicates.

Expand Down Expand Up @@ -331,6 +360,7 @@ providers: [
provide: 'COLLECTION_SERVICE_OPTIONS',
useValue: {
allowFetchedDuplicates: environment.production,
errReporter: environment.production ? undefined : console.error
}
},
]
Expand All @@ -353,7 +383,10 @@ interface CollectionServiceOptions {
allowFetchedDuplicates?: boolean;

// in case of duplicate detection, `onError` callback function (if provided) will be called with this value as an argument
onDuplicateErrCallbackParam?: any;
onDuplicateErrCallbackParam?: any;

// print errors. Example: ` errReporter: environment.production ? undefined : console.error `
errReporter?: (...args: any[]) => any;
}
```

Expand Down Expand Up @@ -647,6 +680,9 @@ interface CollectionServiceOptions {

// in case of duplicate detection, `onError` callback function (if provided) will be called with this value as an argument
onDuplicateErrCallbackParam?: any;

// print errors. Example: ` errReporter: environment.production ? undefined : console.error `
errReporter?: (...args: any[]) => any;
}
```

Expand Down
44 changes: 40 additions & 4 deletions libs/ngx-collection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,36 @@ The equality of items will be checked using the comparator that you can replace

The comparator will compare items using `===` first, then it will use id fields.

You can check the id fields list of the default comparator in [comparator.ts](projects/ngx-collection/src/lib/comparator.ts) file.
You can check the default id fields list of the default comparator in [comparator.ts](projects/ngx-collection/src/lib/comparator.ts) file.

You can easily configure this using Angular DI:
```ts
{
provide: 'COLLECTION_SERVICE_OPTIONS',
useValue: {
comparatorFields: ['uuId', 'url'],
}
},
```

or by re-instantiating a Comparator:

```ts
export class NotificationsCollectionService extends CollectionService<Notification> {
override init() {
this.setComparator(new Comparator(['signature']))
}
}
```

or by providing your own Comparator - it can be a class, implementing `ObjectsComparator` interface, or a function, implementing `ObjectsComparatorFn`.

## Comparator fields

In default comparator, every item in the list of fields can be:
1. `string` - if both objects have this field, and values are equal, objects are equal.
If both objects have this field, and values are not equal - objects are not equal and __comparison stops__.
2. `string[]` - composite field: if both objects have every field enlisted, and every value is equal, objects are equal.

## Duplicates prevention

Expand All @@ -254,10 +283,10 @@ To find a duplicate, items will be compared by comparator - objects equality che

A collection might have duplicates because of some data error or because of wrong fields in the comparator - you can redefine them.

If a duplicate is detected, the item will not be added to the collection and an error will be printed to the console (if `window.console` does exist).
If a duplicate is detected, the item will not be added to the collection and an error will be sent to the `errReporter` (if `errReporter` is set).
You can call `setThrowOnDuplicates('some message')` to make Collection Service throw an exception with the message you expect.

Method `read()` by default will put returned items to the collection even if they have duplicates, but `console.error` will be raised (if `window.console` exists),
Method `read()` by default will put returned items to the collection even if they have duplicates, but `errReporter` will be called (if `errReporter` is set),
because in this case you have a chance to damage your data in future `update()`.
You can call `setAllowFetchedDuplicates(false)` to instruct `read()` to not accept items lists with duplicates.

Expand Down Expand Up @@ -331,6 +360,7 @@ providers: [
provide: 'COLLECTION_SERVICE_OPTIONS',
useValue: {
allowFetchedDuplicates: environment.production,
errReporter: environment.production ? undefined : console.error
}
},
]
Expand All @@ -353,7 +383,10 @@ interface CollectionServiceOptions {
allowFetchedDuplicates?: boolean;

// in case of duplicate detection, `onError` callback function (if provided) will be called with this value as an argument
onDuplicateErrCallbackParam?: any;
onDuplicateErrCallbackParam?: any;

// print errors. Example: ` errReporter: environment.production ? undefined : console.error `
errReporter?: (...args: any[]) => any;
}
```

Expand Down Expand Up @@ -647,6 +680,9 @@ interface CollectionServiceOptions {

// in case of duplicate detection, `onError` callback function (if provided) will be called with this value as an argument
onDuplicateErrCallbackParam?: any;

// print errors. Example: ` errReporter: environment.production ? undefined : console.error `
errReporter?: (...args: any[]) => any;
}
```

Expand Down
8 changes: 4 additions & 4 deletions libs/ngx-collection/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ngx-collection",
"version": "1.2.7",
"version": "1.2.8",
"license": "MIT",
"author": {
"name": "Evgeniy OZ",
Expand All @@ -14,9 +14,9 @@
"private": false,
"peerDependencies": {
"@angular/core": "^15.0.1",
"@ngrx/component-store": "^15.0.0-rc.0",
"@ngrx/effects": "^15.0.0-rc.0",
"@ngrx/store": "^15.0.0-rc.0",
"@ngrx/component-store": "^15.0.0",
"@ngrx/effects": "^15.0.0",
"@ngrx/store": "^15.0.0",
"rxjs": "^7.5.7"
},
"devDependencies": {
Expand Down
15 changes: 10 additions & 5 deletions libs/ngx-collection/src/lib/collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,12 @@ export interface RefreshManyParams<T> {
export type DuplicatesMap<T> = Record<number, Record<number, T>>;

export interface CollectionServiceOptions {
comparatorFields?: string[];
comparatorFields?: (string | string[])[];
comparator?: ObjectsComparator | ObjectsComparatorFn;
throwOnDuplicates?: string;
allowFetchedDuplicates?: boolean;
onDuplicateErrCallbackParam?: any;
errReporter?: (...args: any[]) => any;
}

@Injectable()
Expand Down Expand Up @@ -144,6 +145,7 @@ export class CollectionService<T, UniqueStatus = any, Status = any> extends Comp
protected throwOnDuplicates?: string;
protected allowFetchedDuplicates: boolean = true;
protected onDuplicateErrCallbackParam = {status: 409};
protected errReporter?: (...args: any[]) => any;

protected addToUpdatingItems(item: Partial<T> | Partial<T>[]) {
this.patchState((s) => {
Expand Down Expand Up @@ -290,11 +292,11 @@ export class CollectionService<T, UniqueStatus = any, Status = any> extends Comp
if (this.throwOnDuplicates) {
throw new Error(this.throwOnDuplicates);
}
if (console) {
console.error('Duplicate can not be added to collection:', item, items);
if (this.errReporter) {
this.errReporter('Duplicate can not be added to collection:', item, items);
const duplicates = this.getDuplicates(items);
if (duplicates) {
console.error('Duplicates are found in collection:', duplicates);
this.errReporter('Duplicates are found in collection:', duplicates);
}
}
}
Expand All @@ -314,7 +316,7 @@ export class CollectionService<T, UniqueStatus = any, Status = any> extends Comp
});
this.setOptions(options);
this.init();
Promise.resolve().then(()=> this.postInit());
Promise.resolve().then(() => this.postInit());
}

protected init() {
Expand Down Expand Up @@ -885,6 +887,9 @@ export class CollectionService<T, UniqueStatus = any, Status = any> extends Comp
if (options.onDuplicateErrCallbackParam != null) {
this.onDuplicateErrCallbackParam = options.onDuplicateErrCallbackParam;
}
if (options.errReporter != null && typeof options.errReporter === 'function') {
this.errReporter = options.errReporter;
}
}
}
}
112 changes: 112 additions & 0 deletions libs/ngx-collection/src/lib/comparator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { Comparator } from './comparator';

describe('comparator', () => {
it('single field', () => {
const c = new Comparator(['id']);

expect(c.equal(
{id: 0, name: 'A'},
{id: 0, name: 'B'},
)).toBeTruthy();

expect(c.equal(
{uuId: false, name: 'A'},
{uuId: false, name: 'B'},
['uuId']
)).toBeTruthy();

expect(c.equal(
{uuId: null, name: 'A'},
{uuId: 'UUID', name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{uuId: {}, name: 'A'},
{uuId: 'UUID', name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{uuId: [], name: 'A'},
{uuId: 'UUID', name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{uuId: undefined, name: 'A'},
{uuId: 'UUID', name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{uuId: undefined, name: 'A'},
{uuId: undefined, name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{name: 'A'},
{name: 'B'},
['uuId']
)).toBeFalsy();

expect(c.equal(
{path: 'body/div', name: 'A'},
{path: 'body/div', name: 'B'},
['path']
)).toBeTruthy();

expect(c.equal(
{id: 0, name: 'A'},
{id: 0, name: 'B'},
[['id']]
)).toBeTruthy();

expect(c.equal(
{id: 0, name: 'A'},
{id: 0, name: 'B'},
['id', 'name']
)).toBeTruthy();

expect(c.equal(
{id: 0, name: 'A'},
{name: 'A'},
['id', 'name']
)).toBeTruthy();

expect(c.equal(
{id: 0, name: 'A'},
{id: 1, name: 'A'},
['id', 'name']
)).toBeFalsy();
});

it('multiple fields', () => {
const c = new Comparator([['id', 'status']]);

expect(c.equal(
{id: 0, name: 'A', status: 'N'},
{id: 0, name: 'B', status: 'N'},
)).toBeTruthy();

expect(c.equal(
{id: 0, name: 'A', status: 'N'},
{id: 1, name: 'B', status: 'N'},
)).toBeFalsy();

expect(c.equal(
{id: false, name: 'B', status: 'N'},
{id: false, name: 'B', status: 'M'},
[['id', 'name']]
)).toBeTruthy();

expect(c.equal(
{id: false, name: 'B', status: 'N'},
{id: false, name: 'B', status: 'M'},
[['id', 'status'], ['name']]
)).toBeTruthy();

});

});
Loading

0 comments on commit e426e1a

Please sign in to comment.