Data source

Data sources provide a unified way to manage, filter, sort, group, paginate, and select data for components such as grid, combobox, and listbox.

Overview

The data source package (@sl-design-system/data-source) provides a set of classes for managing collections of data. Instead of passing raw arrays directly to components, you wrap your data in a data source. This gives you a consistent API for filtering, sorting, grouping, pagination, and selection — all of which are automatically reflected in the consuming component.

The class hierarchy is:

  • DataSource — Abstract base class defining the shared interface (filter, sort)
    • ListDataSource — Abstract class that adds grouping, pagination, selection, and reordering
      • ArrayListDataSource — Concrete implementation for in-memory arrays
      • FetchListDataSource — Concrete implementation for lazily-fetched / server-side data

DataSource

DataSource is the abstract base class that all data sources extend. It extends EventTarget, so any component can listen for the sl-update event to react to changes. It defines the core contract:

MemberDescription
filtersA Map of active filters keyed by id
itemsThe filtered and sorted array of view models
sizeThe total number of items after filtering
sortThe current sort configuration (property path or function + direction)
addFilter(id, by, value?)Add a filter by property path or custom function
removeFilter(id)Remove a filter by its id
setSort(by, direction)Set a sort by property path or custom function
removeSort()Clear the current sort
update()Recompute items and emit sl-update

You never instantiate DataSource directly — use one of the concrete subclasses instead.

ListDataSource

ListDataSource extends DataSource and adds support for grouping, pagination, selection, and reordering. Like DataSource, it is abstract — you use ArrayListDataSource or FetchListDataSource instead.

Grouping

Items can be grouped by a property path. Groups are represented as ListDataSourceGroupItem entries interleaved with ListDataSourceDataItem entries in the items array. Groups can be collapsed and expanded:

MethodDescription
setGroupBy(path, labelPath?)Group items by a property path, with an optional label path
removeGroupBy()Remove grouping
expandGroup(id)Expand a collapsed group
collapseGroup(id)Collapse a group
toggleGroup(id, force?)Toggle a group's collapsed state
isGroupCollapsed(id)Check whether a group is collapsed

Pagination

When the pagination option is enabled, only a slice of items is returned based on the current page and page size. This affects the value of size, while totalSize continues to report the total number of items available for pagination:

Method / PropertyDescription
pageThe current page index (zero-based)
pageSizeThe number of items per page (default: 10)
paginationWhether pagination is enabled
sizeThe number of items currently exposed by the data source (e.g. the items on the current page after filtering/pagination)
totalSizeThe total number of items in the data source after filtering but before pagination; use this for paginator/selection logic and page count calculations
setPage(page)Navigate to a specific page
setPageSize(pageSize)Change the number of items per page

When pagination is disabled, size and totalSize are equal. When pagination is enabled, size usually equals pageSize (except on the last page), while totalSize stays constant for the active filters and is what components such as paginators should rely on.

Selection

Selection can be configured as 'single' or 'multiple' via the selects option. The data source tracks selection state and emits sl-selection-change events:

Method / PropertyDescription
selectsThe selection mode: 'single', 'multiple', or undefined
selectedThe number of currently selected items
selectionThe set of selected (or deselected) item ids
select(item)Select an item
deselect(item)Deselect an item
toggle(item, force?)Toggle selection state
isSelected(item)Check whether an item is selected
selectAll()Select all items (multiple mode only)
deselectAll()Deselect all items
areAllSelected()Whether all items are selected
areSomeSelected()Whether some (but not all) items are selected

Options

The ListDataSourceOptions object accepted by subclass constructors supports the following settings:

OptionDescription
filtersInitial set of filters
groupByProperty path for grouping
groupLabelPathProperty path for the group label
groupSortByCustom sort function for groups
groupSortDirectionSort direction for groups ('asc' or 'desc')
paginationEnable pagination
pageInitial page number
pageSizeItems per page
selectsSelection mode ('single' or 'multiple')
sortByInitial sort property path or function
sortDirectionInitial sort direction
getId(item)Return a unique id for an item
getGroupId(item)Return the group id for an item
isSelected(item)Return the initial selected state of an item

ArrayListDataSource

ArrayListDataSource is the concrete data source for in-memory data. Provide it with a plain array and it handles filtering, sorting, grouping, and pagination entirely on the client side.

Usage

import { ArrayListDataSource } from '@sl-design-system/data-source';

interface Person {
  name: string;
  age: number;
  department: string;
}

const people: Person[] = [
  { name: 'Alice', age: 30, department: 'Engineering' },
  { name: 'Bob', age: 25, department: 'Design' },
  { name: 'Carol', age: 35, department: 'Engineering' }
];

const ds = new ArrayListDataSource(people, {
  sortBy: 'name',
  sortDirection: 'asc'
});

// Access the processed items
console.log(ds.items);

Updating data

Use setData(items) to replace the entire dataset. Existing selections are cleaned up to remove ids that no longer exist in the new data:

ds.setData(newPeople);
ds.update();

Filtering

Add filters by property path or with a custom function:

// Filter by property value
ds.addFilter('dept', 'department', 'Engineering');

// Filter with a custom function
ds.addFilter('senior', (person) => person.age >= 30, true);

ds.update();

// Remove a filter
ds.removeFilter('dept');
ds.update();

Sorting

Sort by a property path or a custom comparator:

ds.setSort('age', 'desc');
ds.update();

Grouping

Group items by a property path:

const ds = new ArrayListDataSource(people, {
  groupBy: 'department'
});

Reordering

ArrayListDataSource supports reordering items in the list:

const item = ds.items[2];
const target = ds.items[0];

ds.reorder(item, target, 'before');

FetchListDataSource

FetchListDataSource is the concrete data source for remote or lazily-loaded data. Instead of providing all items up front, you supply a fetchPage callback that is invoked on demand — for example when the user scrolls or navigates to a new page.

Usage

import { FetchListDataSource } from '@sl-design-system/data-source';

const ds = new FetchListDataSource({
  pageSize: 20,
  fetchPage: async ({ page, pageSize, sort }) => {
    const response = await fetch(
      `/api/people?page=${page}&size=${pageSize}`
    );
    const json = await response.json();

    return {
      items: json.data,
      totalItems: json.total
    };
  }
});

How it works

  • Lazy loading: Items are not fetched until they are accessed (via the Proxy-based items array). Accessing an index triggers a page fetch automatically.
  • Placeholders: While a page is loading, placeholder items are returned. Components can check for the ListDataSourcePlaceholder symbol to render loading indicators.
  • Page caching: Each page is fetched only once. Calling update() resets the cache so the next access re-fetches data.

Groups

You can provide an explicit list of groups. Groups are collapsed by default and their items are fetched on demand when expanded:

const ds = new FetchListDataSource({
  pageSize: 10,
  groups: [
    { id: 'eng', label: 'Engineering', size: 50 },
    { id: 'design', label: 'Design', size: 20 }
  ],
  fetchPage: async ({ group, page, pageSize }) => {
    const response = await fetch(
      `/api/people?dept=${group}&page=${page}&size=${pageSize}`
    );
    const json = await response.json();

    return { items: json.data, totalItems: json.total };
  }
});

Custom fetch options

Override getFetchOptions() in a subclass to pass additional parameters (such as filters) to your API:

class MyDataSource extends FetchListDataSource<Person> {
  getFetchOptions(group, page, pageSize) {
    return {
      ...super.getFetchOptions(group, page, pageSize),
      search: this.searchTerm
    };
  }
}

Using with components

A data source is passed to a component as a JavaScript property — not as an HTML attribute. In Lit templates, use the dot-prefix (.dataSource) to bind the property:

html`<sl-grid .dataSource=${dataSource}></sl-grid>`

In Angular templates:

<sl-grid [dataSource]="dataSource"></sl-grid>

Or set it imperatively:

const grid = document.querySelector('sl-grid');
grid.dataSource = dataSource;

Grid

The grid is the primary consumer of data sources. It supports both ArrayListDataSource and FetchListDataSource. You can also pass a plain items array, in which case the grid will create an ArrayListDataSource for you internally.

import { ArrayListDataSource } from '@sl-design-system/data-source';

const ds = new ArrayListDataSource(people, {
  pagination: true,
  pageSize: 10,
  sortBy: 'name'
});

return html`
  <sl-grid .dataSource=${ds}>
    <sl-grid-column path="name"></sl-grid-column>
    <sl-grid-column path="age"></sl-grid-column>
    <sl-grid-column path="department"></sl-grid-column>
  </sl-grid>
`;

Operations performed on the data source — such as filtering, sorting, or pagination — are automatically reflected in the grid.

Grid with remote data

For server-side data, use FetchListDataSource. The grid will trigger page fetches automatically as the user scrolls or navigates:

import {
  FetchListDataSource,
  FetchListDataSourceError
} from '@sl-design-system/data-source';

const ds = new FetchListDataSource({
  pageSize: 10,
  pagination: true,
  fetchPage: async ({ page, pageSize }) => {
    const response = await fetch(
      `/api/people?page=${page}&size=${pageSize}`
    );

    if (!response.ok) {
      throw new FetchListDataSourceError('Failed to fetch', response);
    }

    const { data, total } = await response.json();
    return { items: data, totalItems: total };
  }
});

return html`
  <sl-grid .dataSource=${ds}>
    <sl-grid-column path="name"></sl-grid-column>
    <sl-grid-column path="department"></sl-grid-column>
  </sl-grid>
`;

Paginator

The paginator component shares the same data source as the grid. Pass the same instance to both so the paginator can control pagination:

return html`
  <sl-grid .dataSource=${ds}>
    <sl-grid-column path="name"></sl-grid-column>
  </sl-grid>
  <sl-paginator .dataSource=${ds}></sl-paginator>
`;

Tree

The tree component uses its own data source types — FlatTreeDataSource and NestedTreeDataSource — which are separate from the list data sources documented above. See the tree component documentation for details.

Events

Data sources emit the following events:

EventDescription
sl-updateFired after update() completes. The detail contains a reference to the data source.
sl-selection-changeFired when the selection changes (select, deselect, toggle, selectAll, deselectAll).

Utility types and functions

The package exports several helper types and functions:

ExportDescription
ListDataSourceDataItem<T>A data item wrapper with id, data, selection state, and optional group reference
ListDataSourceGroupItem<T>A group item with id, label, members, and aggregated selection state
isListDataSourceDataItem(item)Type guard for narrowing to ListDataSourceDataItem
isListDataSourceGroupItem(item)Type guard for narrowing to ListDataSourceGroupItem
ListDataSourcePlaceholderSymbol used as a placeholder for items that are being loaded