List

The <smart-list> component renders a list of entities. It is a wrapper that dispatches to one of three built-in child components based on options.mode or HardwareService.isMobile auto-detection. Each mode's child can be replaced via LIST_MODE_COMPONENTS_TOKEN, which provides a partial override map — any mode you specify replaces the default child; any mode you omit falls back to the built-in.


Usage

<smart-list [options]="options" />

Components

ListComponent (<smart-list>)

Main wrapper. Reads options.mode (or falls back to HardwareService.isMobile for auto mobile detection) and renders the appropriate child via NgComponentOutlet. Handles the empty-state display and loading skeleton before delegating to the mode child.

ListDesktopComponent (<smart-list-desktop>)

Default desktop implementation. Uses CDK Table with Tailwind-styled column cells, sortable column headers, and optional remove/detail/item action columns.

ListMobileComponent (<smart-list-mobile>)

Default mobile implementation. Renders a <ul role="list"> with flex rows, one row per field key, plus optional action icons.

ListMasonryGridComponent (<smart-list-masonry-grid>)

Default masonry-grid implementation. Renders a grid of cards that include image thumbnails and configurable field display.

ListBaseComponent (abstract)

Abstract base directive. Extend it to build custom list implementations for any mode. Exposes the following from IListInternalOptions<T>:

  • fields — computed array of { key, options } from model decorators
  • keys — string array of visible column keys (set by initKeys())
  • list — Signal<T[]> from provider
  • loading — Signal<boolean> from provider
  • page — Signal<number> from pagination options
  • totalPages — Signal<number> from pagination options
  • provider — IListProvider<T>
  • itemHandler — resolved item navigation/select handler
  • removeHandler — resolved remove handler
  • cellPipe — ICellPipe<T> for cell value transformation
  • cssClass — string input (alias class)

Methods:

  • initKeys() — populates keys from computed fields
  • handlePageChange(page: number) — triggers provider getData with updated page

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IListOptions<T>>requiredFull list configuration
classInputSignal<string>''External CSS classes forwarded via cssClass()

IListOptions<T>

IListOptions property reference

PropertyTypeDefaultDescription
providerIListProvider<T>requiredData provider (list signal, loading signal, getData callback)
typeanyrequiredModel class decorated with @Model
modeListMode-Force a specific mode; omit to auto-detect via HardwareService.isMobile
paginationIListPaginationOptions-Pagination config (mode, limit, page signal, totalPages signal, load callbacks)
cellPipeICellPipe<T>-Pipe for transforming each cell value
componentFactoriesIListComponentFactories<T>-Top dynamic component factory
sortboolean | { default?: string; defaultDesc?: boolean }-Enable sorting; optionally set a default sort column and direction
detailsboolean | { provider?: IDetailsProvider<T>; componentFactories?: ...; component? }-Enable detail drill-down. The provider is required whenever this is set; the component is optional
itemboolean | { options?: ItemOptions }-Enable item row action (navigate or custom select)
removeboolean | { provider?: IRemoveProvider<T> }-Enable row remove action; optionally provide a custom remove provider
select'multi'-Enable multi-select mode

LIST_MODE_COMPONENTS_TOKEN

InjectionToken that provides a Partial<Record<ListMode, Type<ListBaseComponent<any>>>> override map. Any mode key you include replaces the built-in default child for that mode; any mode key you omit continues to use the built-in default.

Preline mode presets

Every mode ships a Preline-styled preset alongside the default child:

ListModePresetLook
desktopListDesktopPresetComponentPreline table (smart-list-desktop-preset), styling via presentation
mobileListMobilePresetComponentResponsive card grid (smart-list-mobile-preset), image → card image
masonryGridListMasonryGridPresetComponentMasonry card columns (smart-list-masonry-grid-preset), image → card

Apply them via the ready-made map (covers all three modes):

The desktop preset reads the optional IListOptions.presentation field:

Notes:

  • presentation is consumed only by the desktop preset; the standard components ignore it.
  • The mobile preset maps fields onto card anatomy: FieldType.image/logo → card image, first non-image field → title, remaining fields → body text; the item action renders as the card's primary button (label i18n key: details).
  • The masonryGrid preset keeps the base masonry column algorithm and image mechanism (NgOptimizedImage from the model's image field) and restyles items with the same card recipe as the mobile preset; it has no item action buttons (matching the standard masonry child).
  • Preline "Table with search" / "with pagination" / "selectable rows" variants are covered by the existing searchbar feature, smart-paging (PaginationMode.singlePage), and select: 'multi' respectively — not by presentation. Table caption/footer variants are not supported (no API channel in IListOptions).

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  signal,
  ViewEncapsulation,
} from '@angular/core';

import {
  IListOptions,
  LIST_MODE_COMPONENTS_TOKEN,
  ListBaseComponent,
  ListComponent,
  ListMode,
} from '@smartsoft001/angular';
import { Field, FieldType, Model } from '@smartsoft001/models';

// `<smart-list>` reads the columns off the model metadata, so only the fields
// marked `list: true` become keys of the rendered list.
@Model({})
export class DocsUser {
  id = '';

  @Field({ list: true, type: FieldType.text })
  firstName = '';

  @Field({ list: true, type: FieldType.email })
  email = '';

  @Field({ list: true, type: FieldType.text })
  role = '';
}

/**
 * A custom list built on `ListBaseComponent`.
 *
 * The base resolves `keys` from the model metadata, exposes the rows as the
 * `list` signal and the busy flag as `loading`; the implementation only turns
 * that into markup.
 */
@Component({
  selector: 'docs-custom-list',
  template: `
    <table [class]="containerClasses()">
      <thead>
        <tr>
          @for (key of keys; track key) {
            <th class="docs-list__header">{{ key }}</th>
          }
        </tr>
      </thead>
      <tbody>
        @for (row of rows(); track row.id) {
          <tr class="docs-list__row">
            @for (key of keys; track key) {
              <td class="docs-list__cell">{{ cell(row, key) }}</td>
            }
          </tr>
        }
      </tbody>
    </table>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomListComponent extends ListBaseComponent<DocsUser> {
  // ListComponent passes the external class under its aliased name, so the
  // inherited `cssClass` input is used as is - do not redeclare it here.
  containerClasses = computed(() =>
    ['docs-list', this.cssClass()].filter(Boolean).join(' '),
  );

  // `list` is typed as a CDK table data source, which also covers observables
  // and DataSource instances; this example only handles the plain array form.
  rows = computed<DocsUser[]>(() => {
    const value = this.list();
    return Array.isArray(value) ? value : [];
  });

  cell(row: DocsUser, key: string): string {
    return String((row as unknown as Record<string, unknown>)[key] ?? '');
  }
}

/**
 * The list wrapper resolves its body from a map of modes rather than from a
 * single standard-component token, so the implementation is registered under
 * the mode that `IListOptions.mode` asks for. The map is merged over the
 * built-in one, so the other modes keep their own components.
 */
@Component({
  selector: 'docs-list-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [ListComponent],
  providers: [
    {
      provide: LIST_MODE_COMPONENTS_TOKEN,
      useValue: { [ListMode.desktop]: CustomListComponent },
    },
  ],
  template: `<smart-list [options]="options" />`,
})
export class ListCustomExampleComponent {
  // A provider is the list's data source: two signals plus the callback the
  // list calls when it needs a page. A real application hands over an NgRx
  // facade here instead of static rows.
  options: IListOptions<DocsUser> = {
    provider: {
      list: signal<DocsUser[]>([
        { id: '1', firstName: 'Jan', email: 'jan@example.com', role: 'Admin' },
        {
          id: '2',
          firstName: 'Anna',
          email: 'anna@example.com',
          role: 'User',
        },
        {
          id: '3',
          firstName: 'Piotr',
          email: 'piotr@example.com',
          role: 'User',
        },
      ]),
      loading: signal(false),
      getData: () => undefined,
    },
    type: DocsUser,
    mode: ListMode.desktop,
  };
}

Source

The component lives in packages/shared/angular/src/lib/components/list and is documented for Claude Code by the angular-components-list skill.