npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dwtechs/ngx-crud-builder

v0.1.8

Published

An Angular component library providing a full CRUD experience: dynamic reactive forms, configurable data tables, edition dialogs, column management, export, history, and more.

Readme

@dwtechs/ngx-crud-builder

An Angular component library for building dynamic forms and data tables with minimal configuration. It provides a declarative approach to creating CRUD interfaces with validation, dialogs, export, history, and column management.

View the live demo

Compatibility: requires Angular ^21.1 and PrimeNG ^21.1 (@primeng/themes ^21.0, @primeuix/themes ^2.0, primeicons ^7). The library version tracks Angular's major version — use the matching major.

Native renderer (experimental): a provideNativeFormFieldRenderer() adapter exists to render form fields without PrimeNG. It covers all control types but is not production-ready — styling and edge-case behaviour are incomplete. Use providePrimengFormFieldRenderer() for all current integrations.

Highlights

  • Dynamic form generation with 15+ input types and built-in validation
  • Advanced data tables with filtering, pagination, selection, preferences, and export
  • CRUD operations with HTTP integration, archive/restore, and history tracking
  • Customization support for actions, dialogs, labels, and access control

Demo application

The demo app showcases the library through a form builder, a table playground, and a full end-to-end example.

  • / — interactive form demo
  • /table — table configuration playground
  • /full-example — realistic IT request workflow combining forms, tables, access control, and saved views

Requirements

  • Angular ≥ 21
  • PrimeNG ≥ 21

Installation

npm install @dwtechs/ngx-crud-builder

Quick start

1. Add the required providers

The library relies on PrimeNG's ConfirmationService and MessageService, and requires a PrimeNG theme to be configured via providePrimeNG().

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { providePrimeNG } from 'primeng/config';
import { ConfirmationService, MessageService } from 'primeng/api';
import { definePreset } from '@primeuix/themes';
import Aura from '@primeuix/themes/aura';
import { provideCrudLabels } from '@dwtechs/ngx-crud-builder';

const MyPreset = definePreset(Aura, {
  semantic: {
    primary: { /* your brand colours */ },
  },
});

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    providePrimeNG({
      theme: { preset: MyPreset },
    }),
    MessageService,
    ConfirmationService,
    // required – choose 'primeng' or 'native'
    provideFormFieldRenderer('primeng'),
    // optional – defaults to French
    provideCrudLabels({ /* your overrides */ }),
  ],
};

ConfirmationService is used by the table's archive/restore confirmation dialogs.
MessageService is used for the export-failure toast notification.
Both must be provided at the root level (or at the level of the component tree that hosts <tbl-table>).

2. Add the components

// my-page.component.ts
import { TableComponent } from '@dwtechs/ngx-crud-builder';

@Component({
  selector: 'app-my-page',
  imports: [TableComponent],
  template: `
    <tbl-table
      [config]="config"
      [httpCalls]="httpCalls"
      [entityFactory]="factory"
      entityId="users"
      tableTitle="Users">
    </tbl-table>
  `,
})
export class MyPageComponent {
  config: CrudItemOptions[] = [ /* ... */ ];
  httpCalls = { get: ..., create: ..., update: ..., archive: ... };
  factory = () => ({ id: null, name: '' });
}

Configuration tokens

APP_CONFIG

Provides application-level metadata used internally (local storage key prefixes, API prefix, etc.). All fields have sensible defaults so this token is optional.

import { APP_CONFIG } from '@dwtechs/ngx-crud-builder';

{ provide: APP_CONFIG, useValue: {
  title: 'My App',
  appKey: 'my_app',           // prefix for localStorage keys
  storageKeys: {
    TABLE_CONFIG: 'my_app_tableConfig',
  },
  apiPrefix: '/api/',         // default: '/api/'
}}

ACL_ADAPTER

Controls which CRUD operations (create / update / archive / restore) are available based on a functionalityKey. The default grants all permissions. Override it to plug in your own access-control service.

import { ACL_ADAPTER, AclAdapter, CallsPermissions } from '@dwtechs/ngx-crud-builder';

@Injectable({ providedIn: 'root' })
export class AclService implements AclAdapter {
  permissionsFor(functionality: string | undefined): CallsPermissions {
    // return permissions based on user roles / feature flags
    return {
      canCreate: this.userHasRole('EDITOR'),
      canUpdate: this.userHasRole('EDITOR'),
      canArchive: this.userHasRole('ADMIN'),
      canRestore: this.userHasRole('ADMIN'),
    };
  }
}

// app.config.ts
{ provide: ACL_ADAPTER, useExisting: AclService }

When no ACL_ADAPTER is provided, all four operations are enabled for every table.

provideFormFieldRenderer(adapter)

Required when using FormComponent or any form-bearing component (e.g. the table's edition dialog). Omitting it throws a NullInjectorError at runtime.

Choose the adapter that matches your UI stack:

| Value | Description | |---|---| | 'primeng' | PrimeNG controls — float labels, icon fields, full styling support | | 'native' | Plain HTML controls — no PrimeNG dependency, lighter bundle |

import { provideFormFieldRenderer } from '@dwtechs/ngx-crud-builder';

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideFormFieldRenderer('primeng'), // spread: returns Provider[]
  ],
};

APP_FORM_CONFIG

Registers application-level custom validation error messages that are merged with the library's built-in messages (and with field-level customErrorMessages). Use this for app-specific validators shared across all forms.

import { APP_FORM_CONFIG } from '@dwtechs/ngx-crud-builder';

{ provide: APP_FORM_CONFIG, useValue: {
  customErrorMessages: {
    // key = validator error key, value = message shown to the user
    unsafeWords: 'The text contains forbidden words: {words}',
    minDate: 'The date must be at least {days} day(s) from today',
  },
}}

Message templates may include {placeholder} tokens that are interpolated at runtime by the validator.

LOCALE_ID (Angular standard)

Set Angular's locale to match the language of your CRUD_LABELS override. This affects date/number formatting in PrimeNG controls.

import { LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeEn from '@angular/common/locales/en';

registerLocaleData(localeEn, 'en');

// app.config.ts
{ provide: LOCALE_ID, useValue: 'en' }

Complete app.config.ts example

import { ApplicationConfig, LOCALE_ID } from '@angular/core';
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import { provideRouter } from '@angular/router';
import { providePrimeNG } from 'primeng/config';
import { ConfirmationService, MessageService } from 'primeng/api';
import { definePreset } from '@primeuix/themes';
import Aura from '@primeuix/themes/aura';
import {
  ACL_ADAPTER,
  APP_CONFIG,
  APP_FORM_CONFIG,
  provideCrudLabels,
  provideFormFieldRenderer,
} from '@dwtechs/crud-builder';
import { AclService } from './acl.service';
import { routes } from './app.routes';

const MyPreset = definePreset(Aura, {
  semantic: { primary: { /* brand colours */ } },
});

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes),
    providePrimeNG({ theme: { preset: MyPreset } }),
    MessageService,
    ConfirmationService,
    { provide: LOCALE_ID, useValue: 'fr' },
    { provide: APP_CONFIG, useValue: {
        title: 'My App',
        appKey: 'my_app',
        storageKeys: { TABLE_CONFIG: 'my_app_tableConfig' },
        apiPrefix: '/api/',
    }},
    { provide: ACL_ADAPTER, useExisting: AclService },
    { provide: APP_FORM_CONFIG, useValue: {
        customErrorMessages: {
          unsafeWords: 'Texte non autorisé : {words}',
        },
    }},
    provideFormFieldRenderer('primeng'),
    provideCrudLabels({ /* label overrides */ }),
  ],
};

registerLocaleData(localeFr, 'fr');

Internationalisation (i18n)

The library ships with French default labels. No $localize / Angular i18n pipeline is required. All UI strings are provided through a single CRUD_LABELS injection token and overridden per-application with provideCrudLabels().

provideCrudLabels(overrides)

Call this function once in your ApplicationConfig (or in any component/route providers array for a scoped override). Pass only the sections you want to change — the rest falls back to the French defaults automatically.

import { provideCrudLabels } from '@dwtechs/ngx-crud-builder';

provideCrudLabels({
  toolbar: {
    export: 'Export data',
    configureColumns: 'Configure columns',
    refresh: 'Refresh',
  },
  form: {
    reset: 'Reset',
    submit: 'Save',
  },
  validators: {
    required: 'This field is required',
    emailInvalid: 'Invalid email address',
    minlength: 'Minimum {requiredLength} characters required',
    maxlength: 'Maximum {requiredLength} characters allowed',
    min: 'Value must be ≥ {min}',
    max: 'Value must be ≤ {max}',
    invalid: 'Invalid value',
    unknownValue: 'Please select one of the suggested values',
    maxFileSize: 'File exceeds the maximum allowed size',
  },
})

Available label sections

| Section | Applies to | |---|---| | toolbar | Table toolbar buttons (export, refresh, configure columns) | | exportDialog | Export dialog (header, format choices, data scope) | | columnsDialog | Column management dialog | | columnsViews | Saved column views panel | | tableActions | Row action button tooltips (edit, delete) | | tableRegular | Pagination report (currentPageReport(first, last, total)) | | table | Confirmation dialogs, export error toast, default view name | | tableControl | Inline table control inside forms | | history | Audit / history panel column headers | | editionDialog | Edition dialog header and action buttons | | archivedConfig | Archive status filter and label | | form | Form action buttons (reset, submit) and empty state | | arrayElement | Delete button in array sub-forms | | dateControl | Date picker overlay buttons | | richTextEditor | Rich text editor action label | | fileUpload | File upload control labels and messages | | autocomplete | "Create new option" entry label | | picklist | Picklist source/target column headers | | validators | Form validation error messages | | patterns | Regex pattern validation messages | | download | Info message when printing a non-PDF file |

Function-valued labels

Some labels are functions to allow runtime interpolation:

provideCrudLabels({
  table: {
    deleteConfirmationMultiple: (count) =>
      `Are you sure you want to archive these ${count} items?`,
    restoreConfirmationMultiple: (count) =>
      `Are you sure you want to restore these ${count} items?`,
  },
  tableRegular: {
    currentPageReport: (first, last, total) =>
      `Showing ${first} to ${last} of ${total} entries`,
  },
  fileUpload: {
    maxFileSizeExceeded: (fileName, maxFileSize) =>
      `"${fileName}" exceeds the ${maxFileSize}-byte limit.`,
  },
})

Full English override

provideCrudLabels({
  toolbar: {
    export: 'Export data',
    configureColumns: 'Configure columns',
    refresh: 'Refresh',
  },
  exportDialog: {
    header: 'Export data',
    chooseFormat: 'Choose format',
    chooseData: 'Which data?',
    all: 'All',
    selection: 'Selection',
    cancel: 'Cancel',
    export: 'Export',
  },
  columnsDialog: { header: 'Column management', cancel: 'Cancel', save: 'Save' },
  columnsViews: {
    title: 'Views',
    add: 'Add',
    delete: 'Delete',
    rename: 'Rename',
    validate: 'Apply',
    cancel: 'Cancel',
    deleteConfirmation: 'Are you sure you want to delete this view?',
  },
  tableActions: { edit: 'Edit', delete: 'Delete' },
  tableRegular: {
    currentPageReport: (first, last, total) =>
      `Showing ${first} to ${last} of ${total} entries`,
  },
  table: {
    exportFailedTitle: 'Export failed',
    exportFailedDetail: 'An error occurred while exporting data.',
    deleteConfirmationSingle: 'Are you sure you want to archive this item?',
    deleteConfirmationMultiple: (count) =>
      `Are you sure you want to archive these ${count} items?`,
    restoreConfirmationSingle: 'Are you sure you want to restore this item?',
    restoreConfirmationMultiple: (count) =>
      `Are you sure you want to restore these ${count} items?`,
    confirmationHeader: 'Confirmation',
    confirmationCancel: 'Cancel',
    confirmationConfirm: 'Confirm',
    defaultViewName: 'Default',
  },
  tableControl: {
    add: 'Add',
    delete: 'Delete',
    noData: 'No data',
    actionsColumnHeader: 'Actions',
    multiselectValidate: 'Apply',
    multiselectUnsavedChanges: 'Remember to save your changes',
  },
  history: {
    restore: 'Restore',
    updatedAt: 'Updated at',
    updatedBy: 'Updated by',
    property: 'Property',
    value: 'Value',
    previousValue: 'Previous value',
  },
  editionDialog: {
    historyHeader: 'Change history',
    archive: 'Archive',
    modeCreate: 'Create',
    modeConsult: 'View',
    modeEdit: 'Edit',
    cancel: 'Cancel',
    close: 'Close',
  },
  archivedConfig: {
    label: 'Archived',
    labelAt: 'Archived on',
    archived: 'Archived',
    active: 'Active',
  },
  form: { reset: 'Reset', submit: 'Submit', noFields: 'No fields available' },
  arrayElement: { delete: 'Remove' },
  dateControl: { close: 'Close', clear: 'Clear', today: 'Today', validate: 'Apply' },
  richTextEditor: { editHtml: 'Edit HTML' },
  fileUpload: {
    chooseFile: 'Choose a file',
    dragDropLabel: 'Drag & drop files here, or click to select',
    uploadedFilesTitle: 'Uploaded files',
    canAddMore: 'You can add multiple files.',
    cannotAddMore:
      'Only one file allowed. Remove the current file to upload a new one.',
    maxFileSizeExceeded: (fileName, maxFileSize) =>
      `"${fileName}" exceeds the ${maxFileSize}-byte limit.`,
  },
  autocomplete: { newOption: '(new)' },
  picklist: { sourceHeader: 'Available', targetHeader: 'Selected' },
  validators: {
    required: 'This field is required',
    invalid: 'Invalid value',
    unknownValue: 'Please select one of the suggested values',
    emailInvalid: 'Invalid email address',
    minlength: 'Minimum {requiredLength} characters required',
    maxlength: 'Maximum {requiredLength} characters allowed',
    min: 'Value must be ≥ {min}',
    max: 'Value must be ≤ {max}',
    maxFileSize: 'File exceeds the maximum allowed size',
  },
  patterns: {
    nameWithDashes: 'Allowed: letters, spaces and hyphens',
    lettersWithSpaces: 'Allowed: letters and spaces',
    lettersWithoutSpaces: 'Allowed: letters only',
    alphanumericalWithSpaces: 'Allowed: letters, digits and spaces',
    alphanumerical: 'Allowed: letters and digits',
    onlyCaps: 'Allowed: uppercase letters and digits',
    integer: 'An integer is required',
    phoneNumber: 'Invalid phone number format',
    withExtension: 'A file extension is required',
  },
  download: {
    noPdfPrintWarning:
      'The file is not a PDF; printing is not available. It will be downloaded instead.',
  },
})

Building

npm run build:lib

Output is placed in dist/ngx-crud-builder/.

Running unit tests

npm test

Further documentation