or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdaccordion.mdcollections-data.mddialogs.mddrag-drop.mdindex.mdlistbox.mdmenus.mdobservers.mdoverlays.mdplatform-utilities.mdportals.mdscrolling.mdtesting.mdtext-fields.md
tile.json

collections-data.mddocs/

Collections and Data Management

The Angular CDK Collections module provides utilities for managing data collections, selection state, and data sources for components like tables, lists, and trees.

Capabilities

Selection Model

A model for managing selection state in collections with support for single and multiple selection modes.

/**
 * Model for managing selection state
 * @template T Type of items that can be selected
 */
class SelectionModel<T> {
  /**
   * Create a selection model
   * @param multiple - Whether multiple selection is allowed
   * @param initiallySelectedValues - Initially selected values
   * @param emitChanges - Whether to emit change events
   * @param compareWith - Function to compare items for equality
   */
  constructor(
    multiple?: boolean,
    initiallySelectedValues?: T[],
    emitChanges?: boolean,
    compareWith?: (o1: T, o2: T) => boolean
  );
  
  /**
   * Selected values as a sorted array
   */
  readonly selected: T[];
  
  /**
   * Observable that emits when selection changes
   */
  readonly changed: Subject<SelectionChange<T>>;
  
  /**
   * Select one or more values
   * @param values - Values to select
   */
  select(...values: T[]): void;
  
  /**
   * Deselect one or more values
   * @param values - Values to deselect
   */
  deselect(...values: T[]): void;
  
  /**
   * Toggle the selection state of a value
   * @param value - Value to toggle
   */
  toggle(value: T): void;
  
  /**
   * Clear all selections
   */
  clear(): void;
  
  /**
   * Check if a value is selected
   * @param value - Value to check
   * @returns True if the value is selected
   */
  isSelected(value: T): boolean;
  
  /**
   * Check if the selection is empty
   * @returns True if no items are selected
   */
  isEmpty(): boolean;
  
  /**
   * Check if the selection has values
   * @returns True if any items are selected
   */
  hasValue(): boolean;
  
  /**
   * Sort the selected values using a comparison function
   * @param predicate - Comparison function
   */
  sort(predicate?: (a: T, b: T) => number): void;
  
  /**
   * Check if multiple selection is enabled
   * @returns True if multiple selection is enabled
   */
  isMultipleSelection(): boolean;
}

/**
 * Event emitted when selection changes
 * @template T Type of selected items
 */
interface SelectionChange<T> {
  /** The selection model that emitted the change */
  source: SelectionModel<T>;
  
  /** Items that were added to the selection */
  added: T[];
  
  /** Items that were removed from the selection */
  removed: T[];
}

Usage Example:

import { SelectionModel } from '@angular/cdk/collections';

@Component({
  selector: 'app-selectable-list'
})
export class SelectableListComponent {
  items = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];
  selection = new SelectionModel<string>(true, []); // Multiple selection enabled

  constructor() {
    this.selection.changed.subscribe(change => {
      console.log('Selection changed:', change);
    });
  }

  isSelected(item: string): boolean {
    return this.selection.isSelected(item);
  }

  toggle(item: string): void {
    this.selection.toggle(item);
  }

  selectAll(): void {
    this.selection.select(...this.items);
  }

  clearSelection(): void {
    this.selection.clear();
  }

  get selectedItems(): string[] {
    return this.selection.selected;
  }
}

Data Source

Abstract base class for data sources that provide data to data-displaying components.

/**
 * Base class for data sources
 * @template T Type of data items
 */
abstract class DataSource<T> {
  /**
   * Connect to the data source
   * @param collectionViewer - Viewer that will display the data
   * @returns Observable that emits data arrays
   */
  abstract connect(collectionViewer: CollectionViewer): Observable<T[]>;
  
  /**
   * Disconnect from the data source
   * @param collectionViewer - Viewer that was displaying the data
   */
  abstract disconnect(collectionViewer: CollectionViewer): void;
}

/**
 * Interface for collection viewers that display data
 */
interface CollectionViewer {
  /**
   * Observable that emits when the view range changes
   */
  viewChange: Observable<ListRange>;
}

/**
 * Range of items visible in a list
 */
interface ListRange {
  /** Start index of visible range */
  start: number;
  
  /** End index of visible range */
  end: number;
}

Array Data Source

Simple data source implementation backed by an array.

/**
 * Data source backed by an array
 * @template T Type of data items
 */
class ArrayDataSource<T> extends DataSource<T> {
  /**
   * Create an array data source
   * @param data - Initial data array or observable
   */
  constructor(data: T[] | Observable<T[]>);
  
  /**
   * The data array backing this data source
   */
  data: T[];
  
  /**
   * Connect to the data source
   * @param collectionViewer - Collection viewer
   * @returns Observable that emits the data array
   */
  connect(collectionViewer: CollectionViewer): Observable<T[]>;
  
  /**
   * Disconnect from the data source
   * @param collectionViewer - Collection viewer
   */
  disconnect(collectionViewer: CollectionViewer): void;
}

Usage Example:

import { ArrayDataSource } from '@angular/cdk/collections';

@Component({
  selector: 'app-data-list'
})
export class DataListComponent {
  items = ['Apple', 'Banana', 'Cherry', 'Date'];
  dataSource = new ArrayDataSource(this.items);

  addItem(item: string): void {
    this.items.push(item);
    this.dataSource.data = [...this.items]; // Trigger update
  }

  removeItem(index: number): void {
    this.items.splice(index, 1);
    this.dataSource.data = [...this.items]; // Trigger update
  }
}

Unique Selection Dispatcher

Service for managing unique selections across multiple components.

/**
 * Service for dispatching unique selection events
 */
class UniqueSelectionDispatcher implements OnDestroy {
  /**
   * Listen for selection events with a given ID and name
   * @param id - Unique identifier for the selection group
   * @param name - Name of the selection within the group
   * @returns Observable that emits when other selections change
   */
  listen(id: string, name: string): Observable<UniqueSelectionDispatcherListener>;
  
  /**
   * Notify that a selection has been made
   * @param id - Unique identifier for the selection group
   * @param name - Name of the selection within the group
   */
  notify(id: string, name: string): void;
  
  /**
   * Clean up resources
   */
  ngOnDestroy(): void;
}

/**
 * Event emitted by unique selection dispatcher
 */
interface UniqueSelectionDispatcherListener {
  /** ID of the selection group */
  id: string;
  
  /** Name of the selection that changed */
  name: string;
}

View Repeater Strategies

Strategies for efficiently rendering and managing repeated views in collections.

/**
 * Interface for view repeating strategies
 * @template T Type of data items
 * @template R Type of repeated views
 * @template C Type of context
 */
interface ViewRepeater<T, R, C> {
  /**
   * Apply changes to the view repeater
   * @param changes - Collection of changes to apply
   */
  applyChanges(changes: IterableChanges<T>): void;
  
  /**
   * Detach a view at the given index
   * @param index - Index of view to detach
   * @returns Detached view
   */
  detach(index: number): R;
  
  /**
   * Move a view from one index to another
   * @param oldIndex - Current index
   * @param newIndex - Target index
   */
  move(oldIndex: number, newIndex: number): void;
}

/**
 * View repeater strategy that recycles views
 * @template T Type of data items
 * @template R Type of repeated views
 * @template C Type of context
 */
class RecycleViewRepeaterStrategy<T, R, C> implements ViewRepeater<T, R, C> {
  /**
   * Apply changes to recycle views
   * @param changes - Collection changes
   */
  applyChanges(changes: IterableChanges<T>): void;
  
  /**
   * Detach view at index
   * @param index - Index to detach
   */
  detach(index: number): R;
  
  /**
   * Move view between indices
   * @param oldIndex - Current index
   * @param newIndex - Target index
   */
  move(oldIndex: number, newIndex: number): void;
}

/**
 * View repeater strategy that disposes views
 * @template T Type of data items
 * @template R Type of repeated views
 * @template C Type of context
 */
class DisposeViewRepeaterStrategy<T, R, C> implements ViewRepeater<T, R, C> {
  /**
   * Apply changes by disposing old views
   * @param changes - Collection changes
   */
  applyChanges(changes: IterableChanges<T>): void;
  
  /**
   * Detach and dispose view at index
   * @param index - Index to detach
   */
  detach(index: number): R;
  
  /**
   * Move view between indices
   * @param oldIndex - Current index
   * @param newIndex - Target index
   */
  move(oldIndex: number, newIndex: number): void;
}

Tree Data Structures

Utilities for working with tree data in flat or nested formats.

/**
 * Base tree control for managing tree data and expansion state
 * @template T Type of tree nodes
 * @template K Type of tree node keys
 */
abstract class BaseTreeControl<T, K = T> {
  /**
   * Data nodes in the tree
   */
  dataNodes: T[];
  
  /**
   * Selection model for tracking expanded nodes
   */
  expansionModel: SelectionModel<K>;
  
  /**
   * Get the level of a data node
   */
  getLevel: (dataNode: T) => number;
  
  /**
   * Check if a data node is expandable
   */
  isExpandable: (dataNode: T) => boolean;
  
  /**
   * Expand a tree node
   * @param dataNode - Node to expand
   */
  expand(dataNode: T): void;
  
  /**
   * Collapse a tree node
   * @param dataNode - Node to collapse
   */
  collapse(dataNode: T): void;
  
  /**
   * Toggle the expansion state of a node
   * @param dataNode - Node to toggle
   */
  toggle(dataNode: T): void;
  
  /**
   * Expand all expandable nodes
   */
  expandAll(): void;
  
  /**
   * Collapse all nodes
   */
  collapseAll(): void;
  
  /**
   * Check if a node is expanded
   * @param dataNode - Node to check
   * @returns True if expanded
   */
  isExpanded(dataNode: T): boolean;
  
  /**
   * Get descendants of a node
   * @param dataNode - Parent node
   * @returns Array of descendant nodes
   */
  getDescendants(dataNode: T): T[];
  
  /**
   * Expand all descendants of a node
   * @param dataNode - Parent node
   */
  expandDescendants(dataNode: T): void;
  
  /**
   * Collapse all descendants of a node
   * @param dataNode - Parent node
   */
  collapseDescendants(dataNode: T): void;
}

/**
 * Tree control for flat tree data
 * @template T Type of tree nodes
 * @template K Type of tree node keys
 */
class FlatTreeControl<T, K = T> extends BaseTreeControl<T, K> {
  /**
   * Create a flat tree control
   * @param getLevel - Function to get node level
   * @param isExpandable - Function to check if node is expandable
   * @param options - Optional configuration
   */
  constructor(
    getLevel: (dataNode: T) => number,
    isExpandable: (dataNode: T) => boolean,
    options?: FlatTreeControlOptions<T, K>
  );
}

/**
 * Tree control for nested tree data
 * @template T Type of tree nodes
 * @template K Type of tree node keys
 */
class NestedTreeControl<T, K = T> extends BaseTreeControl<T, K> {
  /**
   * Create a nested tree control
   * @param getChildren - Function to get child nodes
   * @param options - Optional configuration
   */
  constructor(
    getChildren: (dataNode: T) => Observable<T[]> | T[] | undefined | null,
    options?: NestedTreeControlOptions<T, K>
  );
}

/**
 * Options for flat tree control
 */
interface FlatTreeControlOptions<T, K> {
  trackBy?: (dataNode: T) => K;
}

/**
 * Options for nested tree control
 */
interface NestedTreeControlOptions<T, K> {
  trackBy?: (dataNode: T) => K;
}

Usage Patterns

Multi-Select List with Selection Model

import { SelectionModel } from '@angular/cdk/collections';

interface ListItem {
  id: number;
  name: string;
}

@Component({
  selector: 'app-multi-select-list',
  template: `
    <div class="list-container">
      <div class="list-header">
        <label>
          <input 
            type="checkbox" 
            [checked]="isAllSelected()"
            [indeterminate]="isIndeterminate()"
            (change)="toggleAll()">
          Select All
        </label>
      </div>
      
      <div *ngFor="let item of items" class="list-item">
        <label>
          <input 
            type="checkbox"
            [checked]="selection.isSelected(item)"
            (change)="selection.toggle(item)">
          {{ item.name }}
        </label>
      </div>
      
      <div class="list-footer">
        Selected: {{ selection.selected.length }} / {{ items.length }}
      </div>
    </div>
  `
})
export class MultiSelectListComponent {
  items: ListItem[] = [
    { id: 1, name: 'Item 1' },
    { id: 2, name: 'Item 2' },
    { id: 3, name: 'Item 3' },
    { id: 4, name: 'Item 4' }
  ];

  selection = new SelectionModel<ListItem>(true, []);

  isAllSelected(): boolean {
    return this.selection.selected.length === this.items.length;
  }

  isIndeterminate(): boolean {
    return this.selection.selected.length > 0 && !this.isAllSelected();
  }

  toggleAll(): void {
    if (this.isAllSelected()) {
      this.selection.clear();
    } else {
      this.selection.select(...this.items);
    }
  }
}

Custom Data Source for API Data

import { DataSource, CollectionViewer } from '@angular/cdk/collections';
import { BehaviorSubject, Observable } from 'rxjs';

interface User {
  id: number;
  name: string;
  email: string;
}

class UserDataSource extends DataSource<User> {
  private dataSubject = new BehaviorSubject<User[]>([]);
  private loadingSubject = new BehaviorSubject<boolean>(false);

  loading$ = this.loadingSubject.asObservable();

  constructor(private userService: UserService) {
    super();
  }

  connect(collectionViewer: CollectionViewer): Observable<User[]> {
    return this.dataSubject.asObservable();
  }

  disconnect(collectionViewer: CollectionViewer): void {
    this.dataSubject.complete();
    this.loadingSubject.complete();
  }

  loadUsers(filter = '', sortDirection = 'asc', pageIndex = 0, pageSize = 10): void {
    this.loadingSubject.next(true);

    this.userService.getUsers(filter, sortDirection, pageIndex, pageSize)
      .subscribe(users => {
        this.dataSubject.next(users);
        this.loadingSubject.next(false);
      });
  }
}

@Component({
  selector: 'app-user-table',
  template: `
    <div *ngIf="dataSource.loading$ | async">Loading...</div>
    <div *ngFor="let user of dataSource.connect() | async">
      {{ user.name }} - {{ user.email }}
    </div>
  `
})
export class UserTableComponent implements OnInit {
  dataSource: UserDataSource;

  constructor(private userService: UserService) {
    this.dataSource = new UserDataSource(userService);
  }

  ngOnInit(): void {
    this.dataSource.loadUsers();
  }
}