ng-qubee 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -176,6 +176,27 @@ The NestJS driver generates URIs compatible with [nestjs-paginate](https://githu
176
176
  - **Limit** is composed as `limit=15`
177
177
  - **Page** is composed as `page=1`
178
178
 
179
+ ### Per-component instances
180
+
181
+ By default, `provideNgQubee()` / `NgQubeeModule.forRoot()` register `NgQubeeService` at the environment injector, so every component that injects it shares the same query-builder state. If you need a dedicated instance — e.g. a feature component whose filters and pagination must not bleed into the app-wide one — spread `provideNgQubeeInstance()` into the component's `providers`:
182
+
183
+ ```typescript
184
+ import { Component } from '@angular/core';
185
+ import { NgQubeeService, provideNgQubeeInstance } from 'ng-qubee';
186
+
187
+ @Component({
188
+ selector: 'app-product-list',
189
+ standalone: true,
190
+ providers: [...provideNgQubeeInstance()],
191
+ template: '...'
192
+ })
193
+ export class ProductListComponent {
194
+ constructor(private _qb: NgQubeeService) {}
195
+ }
196
+ ```
197
+
198
+ The component gets its own `NgQubeeService`, `NestService`, and `PaginationService`. The driver, strategies, and options are inherited from the environment injector configured by `provideNgQubee()` — you still configure the library once at bootstrap.
199
+
179
200
  ## Query Builder API
180
201
 
181
202
  For composing queries, the first step is to inject the proper NgQubeeService:
@@ -337,6 +358,94 @@ Limit validation is driver-scoped — each request strategy enforces its own acc
337
358
 
338
359
  Non-integer values, zero, negative numbers (other than `-1` for NestJS), `NaN`, and `Infinity` are all rejected.
339
360
 
361
+ #### Auto-reset of page on result-set-changing mutations
362
+
363
+ Any mutation that changes *which records* the server would return also resets `state.page` to `1` automatically. Staying on page 5 of an old result set after changing filters is almost always a bug, so the library makes the reset explicit:
364
+
365
+ | Resets page to 1 | Does NOT reset page |
366
+ |---|---|
367
+ | `setLimit()` | `setBaseUrl()` |
368
+ | `setResource()` | `setPage()` |
369
+ | `setSearch()` / `deleteSearch()` | `addFields()` / `deleteFields()` / `deleteFieldsByModel()` |
370
+ | `addFilter()` / `deleteFilters()` | `addIncludes()` / `deleteIncludes()` |
371
+ | `addFilterOperator()` / `deleteOperatorFilters()` | `addSelect()` / `deleteSelect()` |
372
+ | `addSort()` / `deleteSorts()` | |
373
+
374
+ Rule of thumb: if a mutation changes the record *set* (filters, sort, search, limit, resource), page resets. If it only changes the record *shape* (fields, includes, select), page stays. If you intentionally want to keep the previous page number, call `setPage(n)` again after the mutation.
375
+
376
+ ### Pagination navigation
377
+
378
+ The service exposes a fluent navigation surface so you can wire a standard paginator UI (Prev / N of M / Next) with no manual bookkeeping. All navigation methods return `this` and can be chained.
379
+
380
+ ```typescript
381
+ this._ngQubeeService.nextPage().generateUri().subscribe(uri => /* fire the request */);
382
+ this._ngQubeeService.previousPage();
383
+ this._ngQubeeService.firstPage();
384
+ this._ngQubeeService.lastPage();
385
+ this._ngQubeeService.goToPage(3);
386
+ ```
387
+
388
+ #### Auto-sync from `PaginationService.paginate()`
389
+
390
+ When you hand a paginated response to `PaginationService.paginate()`, the library automatically copies the response's `page` and `lastPage` back into the query-builder state. That means `lastPage()`, `goToPage(n)` bounds checks, and the predicates below become accurate immediately — you don't thread the collection's `lastPage` back in yourself.
391
+
392
+ ```typescript
393
+ this._paginationService.paginate(response); // auto-writes page + lastPage
394
+ this._ngQubeeService.lastPage(); // now safe; jumps to the last page
395
+ ```
396
+
397
+ The auto-sync only flips `isLastPageKnown` to `true` when the response carries a **positive integer** `lastPage`. Server-emitted `0` (empty collection) and absent fields leave the flag `false` — the helpers fall back to their conservative defaults.
398
+
399
+ #### Predicates and accessors
400
+
401
+ Template-safe methods for driving button disable-states and labels:
402
+
403
+ ```typescript
404
+ qb.isFirstPage(); // true on page 1
405
+ qb.isLastPage(); // true only when bounds known and page === lastPage
406
+ qb.hasNextPage(); // true when bounds unknown, or page < lastPage
407
+ qb.hasPreviousPage(); // true when page > 1
408
+ qb.currentPage(); // state.page (always safe)
409
+ qb.totalPages(); // state.lastPage (throws if never synced)
410
+ ```
411
+
412
+ Angular template wiring:
413
+
414
+ ```html
415
+ <button [disabled]="qb.isFirstPage()" (click)="qb.previousPage()">Prev</button>
416
+ <span>Page {{ qb.currentPage() }} of {{ qb.totalPages() }}</span>
417
+ <button [disabled]="qb.isLastPage()" (click)="qb.nextPage()">Next</button>
418
+ ```
419
+
420
+ For the `qb.totalPages()` template usage above, either call `paginate()` at least once before the template renders, or guard the display with `*ngIf="qb.hasNextPage() || !qb.isFirstPage()"` / by reading `qb.nest().isLastPageKnown` directly.
421
+
422
+ #### Error behavior
423
+
424
+ | Helper | Throws | When |
425
+ |---|---|---|
426
+ | `nextPage()` | — | Never throws. No-op when already at `lastPage` (bounds known). |
427
+ | `previousPage()` | — | Never throws. No-op at page 1. |
428
+ | `firstPage()` | — | Never throws. |
429
+ | `lastPage()` | `PaginationNotSyncedError` | `paginate()` has never run (`state.isLastPageKnown` is `false`). |
430
+ | `goToPage(n)` | `InvalidPageNumberError` | `n` is not a positive integer, or exceeds `lastPage` when bounds are known. |
431
+ | `isFirstPage()` / `isLastPage()` / `hasNextPage()` / `hasPreviousPage()` | — | Template-safe. Conservative defaults when bounds unknown. |
432
+ | `currentPage()` | — | Always safe. |
433
+ | `totalPages()` | `PaginationNotSyncedError` | `paginate()` has never run. Guard with `nest().isLastPageKnown` for a non-throwing check. |
434
+
435
+ #### Guarding the imperative "jump to last" button
436
+
437
+ `lastPage()` and `totalPages()` need a synced response. A safe pattern:
438
+
439
+ ```html
440
+ <button
441
+ [disabled]="!qb.nest().isLastPageKnown || qb.isLastPage()"
442
+ (click)="qb.lastPage()">
443
+ Last
444
+ </button>
445
+ ```
446
+
447
+ The `isLastPageKnown` read short-circuits before `qb.lastPage()` could throw.
448
+
340
449
  ### Retrieving data
341
450
  URI is generated invoking the _generateUri_ method of the NgQubeeService. An observable is returned and the URI will be emitted:
342
451