Collection
The Collection<T>
class represents a reactive group of items in the ReactiveModel system. It handles loading,
filtering, and managing multiple instances of a defined Item
, ensuring synchronization with a shared registry and
support for dynamic updates from external data sources.
This class is designed to work with domain-specific Item
models and their corresponding ICollectionProvider
,
enabling reactive collections that automatically reflect backend or external changes.
🧩 Inheritance
Extends:
Model<Collection<T, P>>
Integrates with:
Item<T>
,RegistryFactory
, andICollectionProvider
🧪 Basic Usage
💡 To better understand how providers work and how to implement them, see the providers documentation.
import { Collection } from '@beyond-js/reactive/entities/collection';
import { User } from './user'; // Must extend Item
import { UserProvider } from './user-provider';
class Users extends Collection<User, UserProvider> {
constructor() {
super({
entity: 'users',
provider: UserProvider,
item: User,
});
}
}
---
## 📦 Constructor
```ts
new Collection({
entity: string;
provider?: class implements ICollectionProvider;
item: class extends Item;
nextParamName?: string; // Optional: name of the pagination parameter (default: "next")
})
```
### Parameters
- `entity`: **(required)** Identifier for the collection type
- `provider`: **(optional)** Data provider for remote operations
- `item`: **(required)** The `Item` class to instantiate on data load
- `nextParamName`: **(optional)** Name of the pagination parameter sent to the provider. Defaults to `"next"`. Use this if your provider expects a different key (e.g., `"cursor"`, `"start"`).
---
## 🔑 Key Properties
| Property | Type | Description |
| ---------- | --------------------- | -------------------------------------------------- |
| `entity` | `string` | Name of the collection’s data type |
| `Item` | `typeof Item<T>` | Reference to the `Item` class |
| `provider` | `ICollectionProvider` | Optional data source with `list()` and `publish()` |
| `items` | `T[]` | Current items in the collection |
| `map` | `Map<ItemId, T>` | Internal map of items by ID |
---
## 📡 Events
| Event | Triggered When |
| --------------- | ---------------------------------------- |
| `load` | After successful load and initialization |
| `items.changed` | When items are added or removed |
| `change` | On any mutation to collection state |
---
## 📖 Pagination
### Internal Pagination Handling
- The collection now manages pagination using an internal cursor (by default, the `next` property).
- The parameter name for the cursor sent to the provider can be customized using the `nextParamName` option in the constructor.
- When loading more data (pagination), simply call `load({ update: true })`. The collection will send the current cursor value to the provider using the configured parameter name.
- The provider should return either an array or an object with `{ items, next, total }`.
- The `next` (or custom) value will be updated automatically after each load.
- The `getNext()` method returns the current cursor value, and `getTotal()` returns the total number of available items (if provided).
### Example: Custom Pagination Parameter
```ts
class Users extends Collection<User, UserProvider> {
constructor() {
super({
entity: 'users',
provider: UserProvider,
item: User,
nextParamName: 'start', // Use 'start' as the pagination parameter
});
}
}
```
### Example: Loading More Data
```ts
// Initial load
await users.load({ limit: 20 });
// Load next page (uses internal cursor)
await users.load({ update: true });
```
### Example: Provider Response
```ts
// Provider may return:
{
items: [...], // Array of items for this page
next: 'nextCursor', // Cursor for the next page (or null if no more pages)
total: 100 // (optional) Total number of items available
}
```
### Migration Notes
- If you previously passed the pagination cursor manually in `load`, update your code to rely on the internal handling.
- Use `nextParamName` if your API expects a different cursor parameter name.
---
## ⚙️ Methods
### `async load(args?: ILoadSpecs<T>): Promise<T[]>`
Loads items from the provider using optional filtering, ordering, and pagination arguments.
```ts
await users.load({
where: { status: { equals: 'active' } },
orderBy: { name: 'asc' },
limit: 20,
});
// Pagination is handled internally: you do not need to pass the pagination cursor.
// If the collection has a pagination cursor (e.g., `next` or as configured by `nextParamName`), it will be sent automatically in the request.
// The provider can return either an array or an object with `{ items, next, total }`.
// To fetch the next page, call `load({ update: true })` and the collection will use the latest cursor.
```
> 💡 **Note**: The filters used in the `where` clause are fully customizable.
> The documented structure (e.g., `equals`, `in`, `gt`, etc.) is a recommendation for compatibility with common
> databases like IndexedDB, SQL, or MongoDB.
> You are free to define any structure as long as your provider knows how to interpret it.
### Recommended Operators
- `equals`, `not`, `in`, `notIn`
- `contains`, `startsWith`, `endsWith`
- `gt`, `gte`, `lt`, `lte`
---
### `setItems(data: any[], clear?: boolean): void`
Adds items to the collection from raw data.
If `clear` is true, replaces all current items.
---
### `getProperties(): { items: T[] }`
Returns an object containing the collection's current items.
---
### `getItemProperties(): object[]`
Returns a plain array of property snapshots (`getProperties()`) from each item.
---
### `set(data: Partial<T>): any`
Overrides `Model.set()` to update collection-level properties and emit `change`.
---
## 🔁 Registry Integration
This collection listens to `RegistryFactory` events:
- `record.published`: Adds matching items to the collection if they match filters.
- `record.deleted`: Removes deleted items from the collection.
These events ensure reactive updates across shared registry state.
---
## 🧠 Filtering with `matchesFilters`
Before adding new registries to the collection, they are tested against the defined filters from the latest `load()`
call.
Supports logical `AND` and `OR` conditions, as well as nested filters.
> 🔎 Filters are matched in-memory using the `matchesFilters()` logic after `load()`, so consistent formatting between
> your filters and your provider implementation is encouraged.
---
## 🧩 Example: Collection with Filtered Sync
```ts
await users.load({
where: {
status: { equals: 'active' },
age: { gte: 18 },
},
});
// When a new user is published matching this filter,
// it will be added automatically to the collection.
```
---
## 🔌 Provider Interface
The optional provider should implement:
```ts
interface ICollectionProvider {
list(specs?: any): Promise<any>;
load?(specs?: any): Promise<any>;
publish?(data: any): Promise<any>;
remove?(specs?: any): Promise<any>;
}
```
---
## 📄 License
MIT © BeyondJS
Last updated