ngx-crud-builder
ngx-crud-builder
Angular library for dynamic CRUD generation with configurable routes, forms and views.
Table of Contents
Compatibility
ngx-crud-builder |
Angular Version |
|---|---|
2.x.x |
Angular 22+ |
1.x.x |
Angular 21 |
0.0.x |
Angular 20 / 21 |
Features
- Dynamic CRUD route generation
- Configurable list, form and detail views
- Built-in API service with HTTP methods
- Angular Signals support
- Zoneless compatible
- Angular Material ready
- i18n with
@ngx-translate
Requirements
| Dependency | Version |
|---|---|
@angular/core |
^22.0.0 |
@angular/material |
^22.0.0 |
@angular/router |
^22.0.0 |
@ngx-translate/core |
^18.0.0 |
@ngx-translate/http-loader |
^18.0.0 |
@angular/material-luxon-adapter |
^22.0.0 |
bootstrap |
^5.3.8 |
class-transformer |
^0.5.1 |
ngx-quill |
^31.0.1 |
photoswipe |
^5.4.4 |
reflect-metadata |
^0.2.2 |
sprintf-js |
^1.1.3 |
Installation
npm install ngx-crud-builder
Setup
1. Configure providers in app.config.ts
Register the library providers and initialize dynamic routes using APP_INITIALIZER. The initializer finds your existing admin route and injects the generated CRUD routes as children, preserving your guards and wrapper components.
import { provideRouter, Router } from '@angular/router';
import { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import {
provideCrudConfig,
provideCrudSession,
provideCrudLibrary,
CrudRouteService,
} from 'ngx-crud-builder';
import { routes, routesConfig } from './app.routes';
import { SessionService } from './session.service';
export function initializeCrudRoutes(crudRouteService: CrudRouteService, router: Router) {
return () => {
const crudRoutes = crudRouteService.load({
role: 'admin',
items: [...routesConfig],
includeRole: true,
editPrefix: 'edit',
});
const currentRoutes = router.config;
const adminRoute = currentRoutes.find((r) => r.path === 'admin');
if (adminRoute) {
adminRoute.children = [...(adminRoute.children || []), ...crudRoutes];
router.resetConfig([...currentRoutes]);
}
};
}
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([YourInterceptor])),
provideRouter(routes),
provideCrudConfig({
endpoint: 'https://api.yourapp.com',
storage: 'yourStorageKey',
}),
provideCrudLibrary(),
provideCrudSession(SessionService),
{
provide: APP_INITIALIZER,
useFactory: initializeCrudRoutes,
deps: [CrudRouteService, Router],
multi: true,
},
],
};
2. Implement CrudSessionService
Provide your own session service by implementing the CrudSessionService interface:
import { Injectable } from '@angular/core';
import { CrudSessionService } from 'ngx-crud-builder';
@Injectable({ providedIn: 'root' })
export class SessionService implements CrudSessionService {
can(permission: string): boolean {
// Implement your permission logic here
return true;
}
}
3. Styles
Add to your styles.scss:
@import 'ngx-crud-builder/styles';
Or in your angular.json file:
{
"styles": ["node_modules/ngx-crud-builder/src/styles/index.scss"]
}
For file field components and avatar field components preview, add the following to your global styles file (e.g., styles.css or styles.scss):
@import 'photoswipe/dist/photoswipe.css';
Or in your angular.json file:
{
"styles": ["node_modules/photoswipe/dist/photoswipe.css"]
}
Usage
Route Configuration
Define your CRUD routes and pass them to crudRouteService.load(). By default, all four actions (index, create, edit, show) are generated. Use actions to restrict which ones are created.
import { CrudRoute } from 'ngx-crud-builder';
export const routesConfig: CrudRoute[] = [
{
path: 'users',
group: 'users',
endpoint: 'admin/users',
searchEnabled: true,
fields: [
{ name: 'name', label: 'Name', type: 'text', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true },
{ name: 'role', label: 'Role', type: 'select', required: true },
],
},
{
path: 'products',
group: 'products',
endpoint: 'admin/products',
actions: [{ type: 'index' }, { type: 'create' }, { type: 'edit' }],
fields: [
{ name: 'name', label: 'Product Name', type: 'text', required: true },
{ name: 'price', label: 'Price', type: 'number', required: true },
],
},
];
The following routes are generated automatically for a users resource:
| Path | Action | Component |
|---|---|---|
admin/users |
index | CrudList |
admin/users/create |
create | CrudForm |
admin/users/:id/edit |
edit | CrudForm |
admin/users/:id/details |
show | CrudDetail |
Custom Route Components
Replace the default component for any action by specifying it in the route configuration:
export const routesConfig: CrudRoute[] = [
{
path: 'users',
group: 'users',
endpoint: 'admin/users',
actions: [
{ type: 'index' },
{ type: 'create', component: { component: UsersForm, route: '/create' } },
{ type: 'edit', component: { component: UsersForm, route: '/:id/edit' } },
{ type: 'show', component: { component: UsersDetail, route: '/:id' } },
],
fields: [
{ name: 'name', label: 'Name', type: 'text', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true },
],
},
];
Alternatively, define the routes directly in app.routes.ts and they will take precedence over generated ones:
export const routes: Routes = [
{
path: 'admin',
children: [
{ path: 'users/create', component: UsersForm },
{ path: 'users/:id/edit', component: UsersForm, data: { action: 'edit' } },
{
path: 'users/:id/details',
component: DetailModal,
data: { action: 'show', backRoute: '/admin/users' },
},
],
},
];
Extending the Crud Base Class
Extend Crud to build custom views. The base class provides access to the API service, route data, signals for loading state, and built-in actions like create, edit, destroy and download.
import { Component } from '@angular/core';
import { Crud } from 'ngx-crud-builder';
@Component({
selector: 'app-users-list',
standalone: true,
template: `
@if (loading()) {
<p>Loading...</p>
} @else {
<table>
@for (user of data.items; track user.id) {
<tr>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>
<button (click)="action({ method: 'edit', params: user })">Edit</button>
<button (click)="action({ method: 'destroy', params: user })">Delete</button>
</td>
</tr>
}
</table>
}
`,
})
export class UsersListComponent extends Crud {
override onInit() {
this.load();
}
override onLoadSuccess(response: any) {
this.data = response.data;
}
override onLoadError(error: any) {
console.error('Failed to load users', error);
}
}
Form Builder
Use crud-form-builder to render a dynamic form from a FormFieldInput[] definition:
import { Component } from '@angular/core';
import { CrudFormBuilderComponent, FormFieldInput } from 'ngx-crud-builder';
@Component({
selector: 'app-user-form',
standalone: true,
imports: [CrudFormBuilderComponent],
template: `<crud-form-builder [fields]="fields" (onSubmit)="handleSubmit($event)" />`,
})
export class UserFormComponent {
fields: FormFieldInput[] = [
{ name: 'name', label: 'Full Name', type: 'text', required: true },
{ name: 'email', label: 'Email', type: 'email', required: true },
{
name: 'role',
label: 'Role',
type: 'select',
settings: {
options: [
{ value: 'admin', label: 'Administrator' },
{ value: 'user', label: 'User' },
],
},
},
{
name: 'country',
label: 'Country',
type: 'autocomplete',
settings: {
catalog: {
endpoint: 'admin/countries',
type: 'paginate',
valueKey: 'id',
labelKey: 'name',
},
},
},
{
name: 'avatar',
label: 'Avatar',
type: 'file',
settings: {
fileType: 'image/*',
fileStructure: 'base64',
maxFileSize: 2,
},
},
{
name: 'addresses',
label: 'Addresses',
type: 'list',
settings: { addButton: true, removeButton: true },
children: [
{ name: 'street', label: 'Street', type: 'text', required: true },
{ name: 'city', label: 'City', type: 'text', required: true },
],
},
];
handleSubmit(formValue: any) {
console.log('Form submitted:', formValue);
}
}
Styles
The library provides a default style for the OTP field. If you want to customize it, you can override the .otp-input class in your global styles.
Add styles to styles.scss to include default classes
@import 'ngx-crud-builder/styles';
Variables
The library uses the following CSS variables that you can override in your global styles:
:root {
/* Fields */
--form-divider-height: 1px;
--form-divider-color: gray;
--form-divider-opacity: 0.25;
/* File Field */
--file-field-border-radius: 8px;
/* OTP Input */
--otp-input-width: 36px;
--otp-input-height: 36px;
--otp-input-border-radius: 8px;
--otp-input-border-color: #d0ccca;
--otp-input-border-width: 1px;
--otp-input-border-style: solid;
--otp-input-border-color-focus: #007bff;
--otp-input-border-width-focus: 2px;
--otp-input-border-style-focus: solid;
}
Classes
The library uses the following CSS classes that you can override in your global styles:
.autocomplete-field- Autocomplete field container.avatar-field- Avatar field container.checkbox-group-field- Checkbox group field container.crud-required- Required field indicator (*).date-field-container- Date field container (Bootstrap theme).date-field-icon- Date picker icon (Bootstrap theme).date-field- Date field container.file-field-actions- File field actions.file-field-add-btn- File field add button.file-field-button-col- File field button column.file-field-card- File field card.file-field-item-caption- File field item caption.file-field-item-col- File field item.file-field-item-media- File field item media.file-field-item-remove-btn- File field item remove button.file-field-item- File field item.file-field-label- File field label.file-field-single-file-name-container- File field single file name container.file-field-single-file-name- File field single file name.file-field- File field container.form-divider-label- Form divider label.form-divider- Form divider.form-field-description- Form field description.form-field-icon- Form field icon.form-field-error- Form field error feedback.form-field-subtitle- Form field subtitle.form-field-title-container- Form field title container.form-field-title- Form field title.form-field- Form field container.form-html-editor-field- Form HTML editor field.html-field- HTML field container.list-field- List field container.month-year-field- Month year field container.multiple-autocomplete-field- Multiple autocomplete field container.otp-input- OTP input field.password-field- Password field container.select-field- Select field container.simple-field- Simple field container.toggles-group-field- Toggles group field container
API Reference
provideCrudConfig(config: CrudConfig)
| Property | Type | Description |
|---|---|---|
endpoint |
string |
Base API URL |
storage |
string |
Storage key |
iconClass |
string |
Icon class prefix (default: fa-light) |
routes |
string[] |
Additional route paths to include |
theme |
CrudTheme |
UI theme (default: material) |
CrudRoute
| Property | Type | Description |
|---|---|---|
path |
string |
URL path segment |
group |
string |
Permission group name |
endpoint |
string |
API endpoint |
searchEnabled |
boolean |
Enable search in list view |
fields |
FormFieldInput[] |
Form fields definition |
filters |
FormFieldInput[] |
Filter fields for the list view |
actions |
CrudRouteAction[] |
Override default actions |
CrudRouteService.load(config)
| Property | Type | Description |
|---|---|---|
role |
string |
Role prefix (e.g. admin) |
items |
CrudRoute[] |
Routes to generate |
includeRole |
boolean |
Prefix generated paths with the role |
editPrefix |
string |
Prefix used for edit routes |
ApiService
| Method | Description |
|---|---|
call(request) |
Generic HTTP call (GET, POST, PUT, PATCH, DELETE) |
uploadFile(service, formData) |
Upload files with progress reporting |
download(service, callback) |
Download a file and trigger browser download |
open(service, callback) |
Open a file in a new browser tab |
FormFieldInput
All properties are optional. FormFieldInput is a Partial of FormField with settings typed as Partial<FormFieldSettings>.
| Property | Type | Default | Description |
|---|---|---|---|
name |
string |
'' |
Field key in the form value |
label |
string |
'' |
Display label |
type |
FieldType |
'text' |
Input type |
required |
boolean |
false |
Whether the field is required |
disabled |
boolean |
false |
Whether the field is disabled |
placeholder |
string |
'' |
Placeholder text |
description |
string |
'' |
Hint text shown below the field |
icon |
string |
'' |
CSS class for the prefix icon |
colClass |
string |
'col-lg-6' |
Bootstrap column class for layout |
customContainerClass |
string |
'' |
Extra CSS class on the field container |
full |
boolean |
false |
Whether the field spans full width |
value |
any |
— | Initial value |
children |
FormFieldInput[] |
[] |
Nested fields (used in list/complex) |
settings |
Partial<FormFieldSettings> |
— | Advanced settings (see below) |
FormFieldSettings
| Property | Type | Default | Description |
|---|---|---|---|
opened |
boolean |
false |
Allow free-text input in autocomplete fields |
search |
string |
search |
Enable remote search, where search is the query parameter name, to disable search set to '' |
search_placeholder |
string |
search |
Placeholder for the search input |
addButton |
boolean |
true |
Show add button (list/complex fields) |
removeButton |
boolean |
true |
Show remove button (list/complex fields) |
full |
boolean |
false |
Full width layout inside the field |
fileType |
string |
'' |
Accepted MIME types or extensions (e.g. 'image/*') |
fileStructure |
FileStructureType |
'base64' |
File output format: 'base64' or { [fileKey]: File } |
fileKey |
string |
'' |
Key used when fileStructure is object-based |
fetchKey |
string |
'' |
Response key for the stored file value (file/avatar fields) |
fetchLargeKey |
string |
'' |
Response key for a large/preview URL (file fields) |
max |
number |
— | Maximum value (number fields) |
min |
number |
— | Minimum value (number fields) |
maxString |
string |
— | Maximum value as string (date fields) |
minString |
string |
— | Minimum value as string (date fields) |
pattern |
string |
— | Regular expression pattern for validation (text fields) |
inputmode |
string |
— | Input mode for mobile keyboards (text fields) |
uppercase |
boolean |
false |
Convert input to uppercase (text fields) |
lowercase |
boolean |
false |
Convert input to lowercase (text fields) |
step |
number |
— | Step increment (number fields) |
size |
number |
5 | Size of the OTP field (OTP fields) |
maxFiles |
number |
1 |
Maximum number of files (file fields) |
maxFileSize |
number |
5 |
Maximum file size in MB |
options |
FormFieldOptions[] |
[] |
Static options for checkbox, radio and toggle fields |
preview |
boolean |
true |
Show preview for file fields |
catalog |
Catalog |
— | Remote data source config for select and autocomplete fields |
feeback |
boolean |
false |
Show feedback message for the field |
validator |
RegExp |
— | Regular expression for validation (text fields) |
partialValidator |
RegExp |
— | Partial validation pattern (text fields) |
Translations
Add the following keys to your translation file (e.g. public/i18n/en.json):
{
"crud": {
"file": {
"max_file_size_error": "File size exceeds the maximum allowed size",
"max_files_error": "Maximum number of files exceeded",
"file_error": "Error processing file"
},
"message": {
"error": "An error occurred",
"created": "Created successfully",
"updated": "Updated successfully"
},
"validations": {
"form_invalid": "Form is invalid",
// CUSTOM MESSAGES FOR VALIDATIONS
"required": "This field is required.",
"minlength": "Must have at least {{requiredLength}} characters.",
"pattern": "Invalid format or missing uppercase/number.",
"invalid_format": "Invalid format",
"invalid_characters": "Invalid characters",
"mismatch": "Passwords do not match."
},
"cruds": {
"expiry": {
"invalid_month": "Invalid month",
"expired": "Card has expired"
}
},
"back": "Back",
"button_close": "Close",
// Button to close snack message
"button_close_x": "Close",
"cancel": "Cancel",
"confirm": "Confirm",
"destroy_title": "Delete confirmation",
"end_date": "End date",
"fill": "Please fill in the form",
"first": "First",
"hide-password": "Hide password",
"items_per_page": "Items per page",
"last": "Last",
"next": "Next",
"no_data": "No data",
"of": "of",
"prev": "Previous",
"search_placeholder": "Search...",
"search": "Search",
"select_avatar": "Select avatar",
"select_page": "Select page",
"start_date": "Start date",
"submit": "Submit"
},
"file": {
"select": "Select file(s)",
"selected": "Selected",
"remove": "Remove",
"view": "View",
"download": "Download",
"error": {
"maxFiles": "Maximum {max} files allowed",
"maxSize": "File size exceeds {max}MB limit",
"type": "Invalid file type"
}
}
}
For export file names, add a key following the pattern {role}.{group}.export:
{
"admin.users.export": "users_export"
}
Changelog
2.1.0
- Added
feedbacksetting to show feedback message for fields - Added
validatorRegex for validation pattern
2.0.0
- Angular 22 support
1.2.2
- Form field hint class added for material theme hints, improving styles and angular 22 compatibility.
1.2.1
- File field styling improvements
1.2.0
- Month year field added and otp paste support
1.1.60
- Pagination fixed
1.1.51
- Date field fixed
1.1.5
- Avatar delete button added
- Global styles added
- Added
inputmode,pattern,uppercase, andlowercasesettings - Date field improvements
- Icon class configuration added
Breaking Changes
- Renamed
onCrudLoad→onLoadSuccessandonCrudLoadError→onLoadError
// Before
override onCrudLoad(response: any) { ... }
override onCrudLoadError(error: any) { ... }
// After
override onLoadSuccess(response: any) { ... }
override onLoadError(error: any) { ... }
1.1.4
- Added
minStringandmaxStringstring properties for date field validation - Form validation messages improved
1.1.3
- Added cancel label to destroy dialog
1.1.2
- Bootstrap theme support added
- OTP field added
1.0.2
- Minor style adjustments
- Autocomplete search improvements
1.0.0
- Stable release
0.0.5
- Extended Angular 21 compatibility to all
21.x.xversions - Autocomplete improvements
- Restructured
FormFieldSettingsmodel
Breaking Changes
- Moved properties from
FormFieldtoFormFieldSettings:catalog,maxFiles,maxFileSize,fileType,fileStructure,max,min,step,addButton,removeButton
// Before
{ name: 'file', type: 'file', maxFiles: 3, fileType: 'image/*' }
// After
{ name: 'file', type: 'file', settings: { maxFiles: 3, fileType: 'image/*' } }
0.0.4
- Added
FormFieldSettingstoFormFieldmodel
0.0.3
- README updated
0.0.2
- Renamed package from
crud-componenttongx-crud-builder - Migrated to Angular 21
- Migrated to
inject()API - Fixed injection context errors with zoneless change detection
0.0.1
- Initial release