Skip to main content

build-angular-lists-and-forms

Build ABP Angular list and form pages the framework way — server-side paged/sorted/filtered lists via ListService, and reactive forms with ABP's automatic validation. USE FOR: ListService with component-scoped providers, hookToQuery returning PagedResultDto, get() refresh, page/maxResultCount/filter/sortKey/sortOrder setters, requestStatus$ (isLoading$ is deprecated), totalCount, LIST_QUERY_DEBOUNCE_TIME, reactive forms validated automatically by @ngx-validate/core, DEFAULT_VALIDATION_BLUEPRINTS + withValidationBluePrint localization keys, abp-validation-error, the skipValidation opt-out, and config-driven abp-dynamic-form (DynamicFormComponent + FormFieldConfig). DO NOT USE FOR: proxy consumption, menus, permission directives (angular-ui); proxy generation/regeneration (abp-cli-commands); adding entity/table/toolbar/form-prop extensions to a shipped module (extend-angular-module-ui); component testing (test-angular-ui); server-side application-service or DTO design (build-crud-application-services).

Jump to install

Source facts

Repository
abpframework/abp-skills
Last source activity
July 20, 2026 at 03:24
Detected SKILL.md language
English
Stars
18
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
build-angular-lists-and-forms
description
Build ABP Angular list and form pages the framework way — server-side paged/sorted/filtered lists via ListService, and reactive forms with ABP's automatic validation. USE FOR: ListService with component-scoped providers, hookToQuery returning PagedResultDto, get() refresh, page/maxResultCount/filter/sortKey/sortOrder setters, requestStatus$ (isLoading$ is deprecated), totalCount, LIST_QUERY_DEBOUNCE_TIME, reactive forms validated automatically by @ngx-validate/core, DEFAULT_VALIDATION_BLUEPRINTS + withValidationBluePrint localization keys, abp-validation-error, the skipValidation opt-out, and config-driven abp-dynamic-form (DynamicFormComponent + FormFieldConfig). DO NOT USE FOR: proxy consumption, menus, permission directives (angular-ui); proxy generation/regeneration (abp-cli-commands); adding entity/table/toolbar/form-prop extensions to a shipped module (extend-angular-module-ui); component testing (test-angular-ui); server-side application-service or DTO design (build-crud-application-services).
license
MIT
# Build Angular Lists and Forms (ABP) ABP's Angular UI ships two pieces of infrastructure that AI models routinely re-implement by hand: `ListService` for server-side paged data, and automatic reactive-form validation. Use them instead of hand-rolling paging state or manual error markup. ## When to Use - A list/table page bound to a paged ABP endpoint (`GetListAsync(input)` returning `PagedResultDto<T>`). - Server-side paging, sorting, and search whose subscriptions are cleaned up on component destroy (not leaked). - A reactive create/edit form that should show ABP's localized validation messages automatically. - An arbitrary (non-entity) form you want to drive from a config array rather than template markup. ## When Not to Use - **Consuming generated proxies, menu routes, permission directives** — use **angular-ui**. - **Generating/regenerating the proxies** (`abp generate-proxy -t ng` or the NX generator) — use **abp-cli-commands**. - **Adding columns/actions/form fields to a *shipped* module's page** (Identity, etc.) — use **extend-angular-module-ui**; that's the extension system, a different API. - **Testing these components** — use **test-angular-ui**. - **Designing the server-side app service / DTOs** — use **build-crud-application-services**. ## Lists with `ListService` `ListService` (from `@abp/ng.core`) owns the query state (page, size, filter, sort) and re-runs the request when any of it changes. It **must be component-scoped** — put it in the component's `providers`, never `providedIn: 'root'` — so its subscriptions are cleaned up on destroy. ```ts import { ListService, PagedResultDto } from '@abp/ng.core'; import { Component, inject, OnInit } from '@angular/core'; import { BookService, BookDto } from '../proxy/books'; @Component({ selector: 'app-books', templateUrl: './books.component.html', providers: [ListService], // component-scoped — REQUIRED }) export class BooksComponent implements OnInit { readonly list = inject(ListService); private bookService = inject(BookService); items: BookDto[] = []; count = 0; requestStatus$ = this.list.requestStatus$; // 'idle' | 'loading' | 'success' | 'error' ngOnInit() { // callback receives the composed query, returns Observable<PagedResultDto<T>> this.list .hookToQuery(query => this.bookService.getList(query)) .subscribe(res => { this.items = res.items ?? []; this.count = res.totalCount ?? 0; // read the count off the response }); } } ``` The query object `hookToQuery` passes to your callback is `{ filter, maxResultCount, skipCount, sorting }` — `sorting` is `"field asc|desc"` and `skipCount = page * maxResultCount`, which matches ABP's `PagedAndSortedResultRequestDto`. - **Search:** two-way-bind an input to `list.filter`; the setter re-runs the query. - **Sort:** set `list.sortKey` / `list.sortOrder` (from a table header click). - **Page/size:** set `list.page` / `list.maxResultCount`. - **Refresh after create/update/delete:** call `list.get()` (or `list.getWithoutPageReset()` to stay on the current page). - **Extra query params:** spread the query — `query => this.bookService.getList({ ...query, authorId })`. Debounce is `LIST_QUERY_DEBOUNCE_TIME` (default 300 ms); provide the token alongside `ListService` to change it. ## Reactive forms with automatic validation ABP validates reactive forms through **`@ngx-validate/core`** and renders localized helper text automatically — you do **not** write per-field error `*ngIf` markup. Build a normal `FormGroup` with Angular validators; ABP shows the messages. ```ts form = this.fb.group({ name: ['', [Validators.required, Validators.maxLength(128)]], price: [0, [Validators.required, Validators.min(0)]], }); ``` Message text comes from `DEFAULT_VALIDATION_BLUEPRINTS` (from `@abp/ng.theme.shared`), which maps validator keys (`required`, `email`, `minlength`, …) to localization keys like `AbpValidation::ThisFieldIsRequired`. Override or extend them with `withValidationBluePrint(...)` passed into `provideAbpThemeShared(...)`. - **Custom error rendering:** subclass the `abp-validation-error` component (`ValidationErrorComponent` from `@abp/ng.theme.basic`) and provide `VALIDATION_ERROR_TEMPLATE`. - **Opt out:** put `skipValidation` on a `<form>` (whole form) or on a single `formControlName` input. ## Config-driven dynamic forms For arbitrary (non-entity) forms, drive `<abp-dynamic-form>` (`DynamicFormComponent` from `@abp/ng.components/dynamic-form`) with a `FormFieldConfig[]` array — field types, validators, conditional visibility, nested groups/arrays — instead of writing template markup. This is a **separate validation path** from the reactive-form `@ngx-validate/core` story above: `abp-dynamic-form` does **not** read the theme-shared validation blueprints. It validates from each field's `FormFieldConfig.validators` and renders its own error text (per-field `message`, with an English fallback), so don't expect the automatic localized blueprint messages here — set the message on the field config. ## Validation - Page a list and confirm the network request carries `MaxResultCount`/`SkipCount`/`Sorting` and the grid updates; navigate away and back and confirm no leaked subscriptions (that's the component-scoped provider doing its job). - Submit an invalid form and confirm ABP's localized message appears with **no** hand-written error markup. ## Common Pitfalls - **`ListService` provided in root.** Then it never tears down and leaks across pages. Always component-scoped. - **Reaching for `totalCount$` or `isLoading$`.** `totalCount$` does not exist — read `res.totalCount`. `isLoading$` exists but is **deprecated**; use `requestStatus$`. - **Hand-writing paging state** (page index, skipCount math, debounce). `ListService` already does all of it; wiring your own fights it. - **Manual validation markup.** `@ngx-validate/core` renders messages automatically once the control has Angular validators; adding your own `*ngIf` error blocks duplicates them. - **Confusing the two "dynamic form" APIs.** `abp-dynamic-form` (config-driven, here) is not the entity-extension form (`generateFormFromProps` / `abp-extensible-form`) — that belongs to **extend-angular-module-ui**.
View on GitHub