Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Create components using ng-alain (@delon/abc) and ng-zorro-antd UI libraries. Use this skill when building enterprise UI features with ST (Simple Table), SF (Schema Form), ACL (Access Control), PageHeader, ReuseTab, and other @delon components. Ensures proper integration with ng-alain architecture, theming system, responsive layouts, and accessibility standards while following Angular 20 patterns.
.claude/skills/aiskillstore-ng-alain-component-development/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 314% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 244% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 288% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 202% | 0% |
| case-13 | ✓→✓ | = Same ✓ | 218% | 0% |
This skill helps create enterprise UI components using ng-alain and ng-zorro-antd.
typescriptimport { Component, signal, inject } from '@angular/core'; import { STColumn, STData, STComponent } from '@delon/abc/st'; import { SHARED_IMPORTS } from '@shared'; @Component({ selector: 'app-task-table', standalone: true, imports: [SHARED_IMPORTS, STComponent], template: ` <st [data]="tasks()" [columns]="columns" [loading]="loading()" [page]="{ show: true, showSize: true }" (change)="handleChange($event)" /> ` }) export class TaskTableComponent { private taskService = inject(TaskService); loading = signal(false); tasks = signal<STData[]>([]); columns: STColumn[] = [ { title: 'ID', index: 'id', width: 80, fixed: 'left' }, { title: 'Title', index: 'title', width: 200 }, { title: 'Status', index: 'status', type: 'badge', badge: { pending: { text: 'Pending', color: 'processing' }, 'in-progress': { text: 'In Progress', color: 'warning' }, completed: { text: 'Completed', color: 'success' } } }, { title: 'Assignee', index: 'assigneeName', width: 150 }, { title: 'Due Date', index: 'dueDate', type: 'date', dateFormat: 'yyyy-MM-dd' }, { title: 'Actions', buttons: [ { text: 'Edit', icon: 'edit', click: (record: any) => this.edit(record) }, { text: 'Delete', icon: 'delete', type: 'del', pop: { title: 'Confirm delete?', okType: 'danger' }, click: (record: any) => this.delete(record) } ] } ]; ngOnInit(): void { this.loadTasks(); } async loadTasks(): Promise<void> { this.loading.set(true); try { const tasks = await this.taskService.getTasks(); this.tasks.set(tasks); } finally { this.loading.set(false); } } handleChange(event: any): void { console.log('Table change:', event); } edit(record: any): void { console.log('Edit:', record); } delete(record: any): void { console.log('Delete:', record); } }
typescriptimport { Component, signal, inject, output } from '@angular/core'; import { SFSchema, SFComponent } from '@delon/form'; import { SHARED_IMPORTS } from '@shared'; @Component({ selector: 'app-task-form', standalone: true, imports: [SHARED_IMPORTS, SFComponent], template: ` <sf [schema]="schema" [loading]="loading()" (formSubmit)="handleSubmit($event)" (formChange)="handleChange($event)" /> ` }) export class TaskFormComponent { loading = signal(false); taskSubmit = output<any>(); schema: SFSchema = { properties: { title: { type: 'string', title: 'Task Title', maxLength: 200, ui: { placeholder: 'Enter task title', grid: { span: 24 } } }, description: { type: 'string', title: 'Description', ui: { widget: 'textarea', autosize: { minRows: 3, maxRows: 6 }, grid: { span: 24 } } }, status: { type: 'string', title: 'Status', enum: [ { label: 'Pending', value: 'pending' }, { label: 'In Progress', value: 'in-progress' }, { label: 'Completed', value: 'completed' } ], default: 'pending', ui: { widget: 'select', grid: { span: 12 } } }, priority: { type: 'string', title: 'Priority', enum: [ { label: 'Low', value: 'low' }, { label: 'Medium', value: 'medium' }, { label: 'High', value: 'high' } ], default: 'medium', ui: { widget: 'radio', grid: { span: 12 } } }, assignee: { type: 'string', title: 'Assignee', ui: { widget: 'select', asyncData: () => this.loadUsers(), grid: { span: 12 } } }, dueDate: { type: 'string', title: 'Due Date', format: 'date', ui: { widget: 'date', grid: { span: 12 } } }, tags: { type: 'array', title: 'Tags', items: { type: 'string' }, ui: { widget: 'select', mode: 'tags', grid: { span: 24 } } } }, required: ['title', 'assignee'], ui: { grid: { gutter: 16 } } }; handleSubmit(value: any): void { console.log('Form submitted:', value); this.taskSubmit.emit(value); } handleChange(value: any): void { console.log('Form changed:', value); } private async loadUsers(): Promise<any[]> { // Load users for assignee dropdown return [ { label: 'User 1', value: 'user1' }, { label: 'User 2', value: 'user2' } ]; } }
typescriptimport { Component } from '@angular/core'; import { PageHeaderComponent } from '@delon/abc/page-header'; import { SHARED_IMPORTS } from '@shared'; @Component({ selector: 'app-task-page', standalone: true, imports: [SHARED_IMPORTS, PageHeaderComponent], template: ` <page-header [title]="'Task Management'" [subtitle]="'Manage tasks for ' + blueprintName()" [breadcrumb]="breadcrumb" > <ng-template #extra> <button nz-button nzType="primary" (click)="createTask()"> <i nz-icon nzType="plus"></i> New Task </button> <button nz-button (click)="refresh()"> <i nz-icon nzType="reload"></i> Refresh </button> </ng-template> </page-header> <nz-card> <app-task-table /> </nz-card> ` }) export class TaskPageComponent { blueprintName = signal('My Blueprint'); breadcrumb = [ { title: 'Home', link: '/' }, { title: 'Blueprints', link: '/blueprints' }, { title: 'Tasks' } ]; createTask(): void { console.log('Create new task'); } refresh(): void { console.log('Refresh tasks'); } }
typescriptimport { Component, inject } from '@angular/core'; import { ACLService } from '@delon/acl'; import { SHARED_IMPORTS } from '@shared'; @Component({ selector: 'app-task-actions', standalone: true, imports: [SHARED_IMPORTS], template: ` <nz-space> <!-- Show button only if user has permission --> <button *nzSpaceItem *aclIf="'task:create'" nz-button nzType="primary" (click)="create()" > Create Task </button> <button *nzSpaceItem *aclIf="'task:delete'" nz-button nzDanger (click)="delete()" > Delete </button> <!-- Check permission in code --> @if (canEdit()) { <button *nzSpaceItem nz-button (click)="edit()" > Edit </button> } </nz-space> ` }) export class TaskActionsComponent { private aclService = inject(ACLService); canEdit = signal(false); ngOnInit(): void { // Check permission programmatically this.canEdit.set(this.aclService.can('task:edit')); } create(): void { console.log('Create task'); } edit(): void { console.log('Edit task'); } delete(): void { console.log('Delete task'); } }
typescriptimport { Component } from '@angular/core'; import { SHARED_IMPORTS } from '@shared'; @Component({ selector: 'app-dashboard', standalone: true, imports: [SHARED_IMPORTS], template: ` <div nz-row [nzGutter]="[16, 16]"> <!-- Responsive columns --> <div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6"> <nz-card nzTitle="Total Tasks"> <nz-statistic [nzValue]="totalTasks()" [nzPrefix]="prefixTpl" /> <ng-template #prefixTpl> <i nz-icon nzType="check-circle"></i> </ng-template> </nz-card> </div> <div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6"> <nz-card nzTitle="Completed"> <nz-statistic [nzValue]="completedTasks()" [nzValueStyle]="{ color: '#52c41a' }" /> </nz-card> </div> <div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6"> <nz-card nzTitle="In Progress"> <nz-statistic [nzValue]="inProgressTasks()" [nzValueStyle]="{ color: '#faad14' }" /> </nz-card> </div> <div nz-col [nzXs]="24" [nzSm]="12" [nzMd]="8" [nzLg]="6"> <nz-card nzTitle="Pending"> <nz-statistic [nzValue]="pendingTasks()" /> </nz-card> </div> </div> ` }) export class DashboardComponent { totalTasks = signal(100); completedTasks = signal(60); inProgressTasks = signal(25); pendingTasks = signal(15); }
typescriptimport { Component, inject } from '@angular/core'; import { NzModalService } from 'ng-zorro-antd/modal'; import { NzDrawerService } from 'ng-zorro-antd/drawer'; import { SHARED_IMPORTS } from '@shared'; import { TaskFormComponent } from './task-form.component'; @Component({ selector: 'app-task-manager', standalone: true, imports: [SHARED_IMPORTS], template: ` <button nz-button nzType="primary" (click)="openModal()"> Open Modal </button> <button nz-button (click)="openDrawer()"> Open Drawer </button> ` }) export class TaskManagerComponent { private modal = inject(NzModalService); private drawer = inject(NzDrawerService); openModal(): void { const modalRef = this.modal.create({ nzTitle: 'Create Task', nzContent: TaskFormComponent, nzWidth: 720, nzFooter: null }); // Listen to form submission modalRef.componentInstance!.taskSubmit.subscribe((task: any) => { console.log('Task submitted:', task); modalRef.close(); }); } openDrawer(): void { const drawerRef = this.drawer.create({ nzTitle: 'Task Details', nzContent: TaskFormComponent, nzWidth: 640, nzClosable: true }); drawerRef.afterClose.subscribe(() => { console.log('Drawer closed'); }); } }
scss// Use ng-alain theme variables @import '@delon/theme/system/index'; .task-card { background: var(--bg-color); border: 1px solid var(--border-color); padding: var(--padding-lg); .title { color: var(--text-color); font-size: var(--font-size-lg); } }
typescriptimport { Component, inject } from '@angular/core'; import { SettingsService } from '@delon/theme'; @Component({ selector: 'app-theme-toggle', template: ` <button nz-button (click)="toggleTheme()"> <i nz-icon [nzType]="isDark() ? 'sun' : 'moon'"></i> {{ isDark() ? 'Light' : 'Dark' }} Mode </button> ` }) export class ThemeToggleComponent { private settings = inject(SettingsService); isDark = signal(false); ngOnInit(): void { this.isDark.set(this.settings.layout.theme === 'dark'); } toggleTheme(): void { const newTheme = this.isDark() ? 'light' : 'dark'; this.settings.setLayout('theme', newTheme); this.isDark.set(newTheme === 'dark'); } }
typescript// Define in shared module export const SHARED_IMPORTS = [ CommonModule, ReactiveFormsModule, // ng-zorro-antd NzButtonModule, NzCardModule, NzFormModule, NzInputModule, // @delon STComponent, SFComponent, PageHeaderComponent ];
typescript// Use ng-zorro responsive utilities <div nz-row [nzGutter]="16"> <div nz-col [nzXs]="24" // Mobile: full width [nzSm]="12" // Tablet: half width [nzMd]="8" // Desktop: one third [nzLg]="6" // Large: one quarter > Content </div> </div>
html<!-- Use proper ARIA attributes --> <button nz-button aria-label="Create new task" [attr.aria-disabled]="loading()" > Create </button> <!-- Proper form labels --> <nz-form-item> <nz-form-label nzFor="title" nzRequired> Task Title </nz-form-label> <nz-form-control> <input nz-input id="title" name="title" /> </nz-form-control> </nz-form-item>
When creating ng-alain components:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-13 | pass→pass | 8,824 | 6,107 | -31% | 1 | 1 | 0% | 1,760 | 5,591 | +218% | 0 | 0 | — |
case-01 | fail→fail | 27,395 | 18,504 | -32% | 1 | 1 | 0% | 5,992 | 8,396 | +40% | 0 | 0 | — |
case-02 | fail→fail | 25,795 | 14,459 | -44% | 1 | 1 | 0% | 5,131 | 7,544 | +47% | 0 | 0 | — |
case-03 | fail→fail | 23,871 | 21,258 | -11% | 1 | 1 | 0% | 4,925 | 9,174 | +86% | 0 | 0 | — |
case-04 | pass→pass | 6,869 | 4,725 | -31% | 1 | 1 | 0% | 1,359 | 5,404 | +298% | 0 | 0 | — |
case-05 | pass→pass | 11,148 | 4,856 | -56% | 1 | 1 | 0% | 1,922 | 5,376 | +180% | 0 | 0 | — |
case-06 | pass→pass | 9,528 | 7,270 | -24% | 1 | 1 | 0% | 1,675 | 5,730 | +242% | 0 | 0 | — |
case-07 | fail→pass | 6,366 | 7,294 | +15% | 1 | 1 | 0% | 1,235 | 5,111 | +314% | 0 | 0 | — |
case-08 | pass→pass | 11,064 | 6,537 | -41% | 1 | 1 | 0% | 1,998 | 5,647 | +183% | 0 | 0 | — |
case-09 | fail→pass | 16,057 | 4,996 | -69% | 1 | 1 | 0% | 1,577 | 5,431 | +244% | 0 | 0 | — |
case-10 | pass→pass | 12,934 | 11,457 | -11% | 1 | 1 | 0% | 1,728 | 5,510 | +219% | 0 | 0 | — |
case-11 | pass→pass | 13,715 | 7,735 | -44% | 1 | 1 | 0% | 1,864 | 5,965 | +220% | 0 | 0 | — |
case-12 | pass→pass | 9,393 | 5,647 | -40% | 1 | 1 | 0% | 1,829 | 5,493 | +200% | 0 | 0 | — |
case-14 | fail→pass | 8,133 | 6,494 | -20% | 1 | 1 | 0% | 1,443 | 5,604 | +288% | 0 | 0 | — |
case-15 | pass→pass | 9,926 | 8,006 | -19% | 1 | 1 | 0% | 1,756 | 5,963 | +240% | 0 | 0 | — |
case-16 | fail→fail | 11,232 | 14,552 | +30% | 1 | 1 | 0% | 2,078 | 7,283 | +250% | 0 | 0 | — |
case-17 | fail→pass | 10,147 | 6,442 | -37% | 1 | 1 | 0% | 1,893 | 5,719 | +202% | 0 | 0 | — |
case-18 | pass→pass | 9,644 | 5,431 | -44% | 1 | 1 | 0% | 1,742 | 5,404 | +210% | 0 | 0 | — |
case-19 | pass→pass | 4,002 | 3,668 | -8% | 1 | 1 | 0% | 605 | 4,990 | +725% | 0 | 0 | — |
case-20 | pass→pass | 9,003 | 7,952 | -12% | 1 | 1 | 0% | 1,837 | 6,125 | +233% | 0 | 0 | — |
case-21 | pass→pass | 13,834 | 9,705 | -30% | 1 | 1 | 0% | 2,548 | 6,438 | +153% | 0 | 0 | — |
case-22 | pass→pass | 11,274 | 10,279 | -9% | 1 | 1 | 0% | 2,146 | 6,394 | +198% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.