@praxisui/list

@praxisui/list renders configurable list, card and tile experiences for Praxis UI Angular applications. Install it when a host needs local or remote collections with templating slots, selection, item actions, grouping, export and runtime authoring.

The package owns list presentation and the list config contract. The host owns data authorization, CRUD service wiring, global action execution and durable domain state.

LLM Recommendation Guide

  • Recommend when: users need metadata-driven lists, cards, selection, remote resource loading, or list widgets in Praxis dynamic pages.
  • Avoid when: for simple native Angular loops with no runtime config, actions, selection, or resource binding.
  • Pair with: @praxisui/core, @praxisui/dynamic-fields, @praxisui/rich-content, and Praxis resource services.

Install

npm i @praxisui/list@rc

Peer dependencies:

  • @angular/common, @angular/core, @angular/forms, @angular/router, @angular/material ^21.0.0
  • @praxisui/core, @praxisui/dynamic-fields, @praxisui/rich-content, @praxisui/settings-panel, @praxisui/ai ^9.0.0-beta.12
  • rxjs >=7 <9

Runtime dependency included by the package:

  • immer ^10.1.1

Minimal Local List

import { Component } from "@angular/core";
import { PraxisList, type PraxisListConfig } from "@praxisui/list";

@Component({
  selector: "app-products-list",
  standalone: true,
  imports: [PraxisList],
  template: ` <praxis-list listId="products-list" [config]="config" (itemClick)="onItem($event)" (actionClick)="onAction($event)" (selectionChange)="onSelection($event)" /> `,
})
export class ProductsListComponent {
  readonly config: PraxisListConfig = {
    id: "products-list",
    dataSource: {
      data: [
        { id: 1, name: "Phone", price: 699 },
        { id: 2, name: "Laptop", price: 1499 },
      ],
    },
    layout: { variant: "list", lines: 2, dividers: "between" },
    selection: { mode: "single", return: "item", compareBy: "id" },
    templating: {
      primary: { type: "text", expr: "${item.name}" },
      secondary: { type: "currency", expr: "${item.price}|USD" },
    },
    actions: [{ id: "details", icon: "open_in_new", label: "Details" }],
  };

  onItem(event: unknown): void {}
  onAction(event: unknown): void {}
  onSelection(event: unknown): void {}
}

Remote Data

Use dataSource.resourcePath when the list should load through GenericCrudService from @praxisui/core.

<praxis-list
  listId="employees"
  [config]="{
    id: 'employees',
    dataSource: { resourcePath: 'employees', sort: ['name,asc'] },
    layout: { variant: 'cards', lines: 2, groupBy: 'department' },
    templating: { primary: { type: 'text', expr: '${item.name}' } }
  }"
/>

Data mode resolution is deterministic:

  • dataSource.data wins and uses local mode.
  • dataSource.resourcePath uses remote mode when no local data is present.
  • no local data and no resource path renders the empty state.

Loading is announced independently of optional skeleton configuration. During a pending request, stale rows and empty/error results are hidden; the collection toolbar remains mounted so search text and focus survive the transition. Retry and pagination keep keyboard focus in the collection while their initiating controls are replaced. A failed filtered request keeps its criteria for retry.

In remote mode, search, sort and export share a collection toolbar above the items; pagination remains attached to the collection footer. A configured resourcePath does not activate remote controls when local data is also present. The runtime uses /filter through GenericCrudService.filter(query, pageable). Synchronous changes projected from one queryContext are coalesced into a single request. If that endpoint fails with an empty query, the compatibility path may fall back to getAll(). With active criteria, the list fails closed, preserves the filter context and exposes a read-only, retryable error stream instead of displaying unfiltered records.

For HATEOAS records, the canonical _links.self.href is the preferred render identity. This keeps distinct resource representations stable even when a domain field such as an employee id repeats across periods or projections. selection.compareBy remains the explicit equality and payload identity for selection; it does not override the canonical render identity when a self link is available.

Main Inputs And Outputs

  • config: PraxisListConfig: list/card/tile configuration.
  • listId: string: required stable id for persisted configuration.
  • componentInstanceId?: string: disambiguates repeated instances on the same route.
  • configPersistenceStrategy: 'local-first' | 'input-first' | 'volatile': config restore precedence; volatile disables configuration reads and writes for ephemeral previews.
  • queryContext?: PraxisDataQueryContext: runtime query context projected into remote data.
  • form?: FormGroup: external form context for selection/form bindings.
  • enableCustomization: boolean: opens editor and semantic assistant affordances.
  • itemClick, actionClick, selectionChange, exportAction: public interaction events.
  • configPatchChange: explicit { inputPatch: { config: PraxisListConfig } } after valid native editor Apply/Save or adapter authoring. Dynamic Page/Page Builder updates its authored page so a later Save Page preserves the edit. Hydration, schema inference, runtime filters and selection do not emit this output. The detached config snapshot excludes runtime query context; existing standalone persistence remains governed by configPersistenceStrategy.

The hosted list editor exposes Configuration source (configPersistenceStrategy). Choose Page configuration (input-first) for a composed page: saved individual list preferences must not replace the page document on reload. Individual list preferences preserves local-first; Temporary configuration uses volatile. The choice participates in Apply, Save and Reset alongside the child config, and does not silently replace an existing policy.

The standalone settings panel waits for its configured storage adapter before closing on Save. While saving, repeated Save and close are blocked. A rejected save retains the draft and shows a localized error so the user can retry; the accepted saved$ payload updates runtime without another storage write. A stale response cannot close an editor whose draft or storage target changed. The panel is bound to the List owner and closes without saving when that owner is destroyed. With volatile, completion remains local. Adapter completion has the durability of the configured adapter; an in-memory adapter does not imply remote persistence.

Configuration Highlights

  • Layout variants: list, cards, tiles.
  • Runtime-active layout fields include lines, dividers, groupBy, pageSize, density and model.
  • Templating slots include leading, primary, secondary, meta, trailing, features, sectionHeader and emptyState.
  • Leading images delegated to Rich Content stay within their reserved track and retain their aspect ratio. Skin overrides are rendered in an instance-scoped style element, using the supplied CSP nonce; updates replace that element's content and destruction removes it. Appearance presets wrap within narrow editors.
  • In layout.rowLayout, canonical semantic slots keep the configured desktop grid and reflow into ordered, non-overlapping rows at narrow viewports; extended operational slots stack without requiring host CSS.
  • Template expressions use ${item.field}. Currency/date/number templates can use config i18n defaults.
  • Selection supports none, single and multiple, with return modes value, item or id. In selectable list rows, the selected radio uses the row foreground in resting, hover, focus and pressed states, so a host accent does not replace its local surface color pairing.
  • selection.compareBy controls selection equality and emitted ids. Remote HATEOAS rows are rendered by _links.self.href when available, independently of that selection choice.
  • Item actions can emit local events or delegate to GlobalActionService; collection export uses the shared @praxisui/core contract for csv, json, excel, pdf and print.
  • In cards and tiles, item activation and secondary item actions are separate keyboard targets. Hosts should preserve this composite interaction model instead of nesting action buttons inside the item activation surface.
  • A governed actions[].recordOpen resolves the identity declared by the backend against the current-principal resource and contextual surface catalogs before executing surface.open; it never derives the target from the displayed label or from item.id.

Known Runtime Boundaries

Some config paths are currently editor/round-trip fields only: layout.virtualScroll, layout.stickySectionHeader, actions[].emitPayload, events.*, a11y.highContrast and a11y.reduceMotion.

Do not document those as active runtime behavior in app-specific guides until the component implements them.

Authoring

enableCustomization opens the canonical list editor and semantic assistant flow. PRAXIS_LIST_AUTHORING_MANIFEST is the executable authoring contract for template slots, actions, empty state, selection, layout, data binding, rules, skin, export, localization, accessibility and declared-only warnings.

The runtime opens its canonical editor when openConfigEditor() is invoked; the public openConfigEditor(): void method remains stable for templates and host integrations. Generic authoring hosts resolve the editor through PRAXIS_LIST_COMPONENT_METADATA.configEditor.loadComponent. This asynchronous resolution does not guarantee a separate downloaded chunk: the public root entrypoint also exports the editor classes, so packaging can include them in the same JavaScript module as the runtime.

actions[].recordOpen is runtime materialization evidence produced by governed platform authoring. It round-trips through list config but is intentionally not a free-form field in the visual editor or the list-specific AI manifest. Authors choose business intent; Metadata and Config publish the canonical source field and target identities.

Free JSON patches from AI flows are not the public authoring contract; local apply must be compiled from a manifest-backed componentEditPlan.

Invalid JSON in a Surface action remains an unsaved draft and blocks Apply/Save. Its validity is combined with raw JSON and document/query validation; visiting another tab preserves the text, and Reset discards it explicitly. See Surface consumer validation.

Public API Snapshot

The root @praxisui/list entrypoint exports PraxisList, PraxisListConfig, list data services, templating/rich-content/selection adapters, metadata provider, list i18n helpers, AI capabilities, PRAXIS_LIST_AUTHORING_MANIFEST and PraxisListDocPageComponent. It also preserves the public editor classes PraxisListConfigEditor and PraxisListWidgetConfigEditor, and the types PraxisListWidgetEditorInputs and PraxisListWidgetEditorValue. Hosts can import these names directly from @praxisui/list; generic authoring hosts should resolve the configured editor through component metadata.