ngx-crud-builder
v1.0.2
Published
Angular library for dynamic CRUD generation with configurable routes, forms and views
Readme
ngx-crud-builder
Angular library for dynamic CRUD generation with configurable routes, forms and views.
Table of Contents
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 | ^21.0.0 |
| @angular/material | ^21.0.0 |
| @angular/router | ^21.0.0 |
| @ngx-translate/core | ^17.0.0 |
| @ngx-translate/http-loader | ^17.0.0 |
| @angular/material-luxon-adapter | ^21.0.0 |
Installation
npm install ngx-crud-builderSetup
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 { ApplicationConfig, APP_INITIALIZER } from '@angular/core';
import { provideRouter, Router } from '@angular/router';
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
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 onCrudLoad(response: any) {
this.data = response.data;
}
override onCrudLoadError(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);
}
}API Reference
provideCrudConfig(config: CrudConfig)
| Property | Type | Description |
| ---------- | -------- | ------------ |
| endpoint | string | Base API URL |
| storage | string | Storage key |
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 '' |
| 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) |
| step | number | — | Step increment (number 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 |
| catalog | Catalog | — | Remote data source config for select and autocomplete fields |
Translations
Add the following keys to your translation file (e.g. public/i18n/en.json):
{
"crud": {
"destroy_title": "Delete confirmation",
"back": "Back",
"confirm": "Confirm",
"select_avatar": "Select avatar",
"form_invalid": "Form is invalid",
"no_data": "No data",
"search": "Search",
"search_placeholder": "Search...",
"submit": "Submit",
"fill": "Please fill in the form",
"select_page": "Select page",
"hide-password": "Hide password",
"date_format": "MM/DD/YYYY",
"date_range_format": "MM/DD/YYYY – MM/DD/YYYY",
"start_date": "Start date",
"end_date": "End date",
"items_per_page": "Items per page",
"next": "Next",
"prev": "Previous",
"first": "First",
"last": "Last",
"of": "of",
"button_close": "Close",
"file": {
"max_file_size_error": "File size exceeds the maximum allowed size"
},
"message": {
"error": "An error occurred",
"created": "Created successfully",
"updated": "Updated successfully"
}
},
"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
1.0.1
- Minor style adjustments and autocomplete search improvements
1.0.0
- Initial release
0.0.5
- Extended Angular 21 compatibility to support all
21.x.xversions - Autocomplete improvements
- Restructured
FormFieldSettingsmodel
⚠️ Breaking Changes
catalog,maxFiles,maxFileSize,fileType,fileStructure,max,min,step,addButton,removeButtonproperties moved fromFormFieldtoFormFieldSettings
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
