@shivay_18/ng-crud 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.docx +0 -0
  2. package/README.md +142 -0
  3. package/dist/collection.json +10 -0
  4. package/dist/crud/files/module/components/__name@dasherize__-form/__name@dasherize__-form.component.html.template +48 -0
  5. package/dist/crud/files/module/components/__name@dasherize__-form/__name@dasherize__-form.component.scss.template +46 -0
  6. package/dist/crud/files/module/components/__name@dasherize__-form/__name@dasherize__-form.component.ts.template +121 -0
  7. package/dist/crud/files/module/components/__name@dasherize__-list/__name@dasherize__-delete-dialog.component.ts.template +27 -0
  8. package/dist/crud/files/module/components/__name@dasherize__-list/__name@dasherize__-list.component.html.template +75 -0
  9. package/dist/crud/files/module/components/__name@dasherize__-list/__name@dasherize__-list.component.scss.template +40 -0
  10. package/dist/crud/files/module/components/__name@dasherize__-list/__name@dasherize__-list.component.ts.template +95 -0
  11. package/dist/crud/files/module/components/__name@dasherize__-list/__name@dasherize__.module.ts.template +73 -0
  12. package/dist/crud/files/module/models/__name@dasherize__.model.ts.template +4 -0
  13. package/dist/crud/files/module/services/__name@dasherize__.service.ts.template +42 -0
  14. package/dist/crud/files/standalone/components/__name@dasherize__-form/__name@dasherize__-form.component.html.template +48 -0
  15. package/dist/crud/files/standalone/components/__name@dasherize__-form/__name@dasherize__-form.component.scss.template +46 -0
  16. package/dist/crud/files/standalone/components/__name@dasherize__-form/__name@dasherize__-form.component.ts.template +142 -0
  17. package/dist/crud/files/standalone/components/__name@dasherize__-list/__name@dasherize__-delete-dialog.component.ts.template +30 -0
  18. package/dist/crud/files/standalone/components/__name@dasherize__-list/__name@dasherize__-list.component.html.template +75 -0
  19. package/dist/crud/files/standalone/components/__name@dasherize__-list/__name@dasherize__-list.component.scss.template +40 -0
  20. package/dist/crud/files/standalone/components/__name@dasherize__-list/__name@dasherize__-list.component.ts.template +121 -0
  21. package/dist/crud/files/standalone/models/__name@dasherize__.model.ts.template +4 -0
  22. package/dist/crud/files/standalone/services/__name@dasherize__.service.ts.template +42 -0
  23. package/dist/crud/index.d.ts +3 -0
  24. package/dist/crud/index.js +141 -0
  25. package/dist/crud/index.js.map +1 -0
  26. package/dist/crud/schema.d.ts +7 -0
  27. package/dist/crud/schema.js +3 -0
  28. package/dist/crud/schema.js.map +1 -0
  29. package/dist/crud/schema.json +34 -0
  30. package/package.json +33 -0
package/README.docx ADDED
Binary file
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # ng-crud
2
+
3
+ Angular CRUD generator using Angular Schematics and Angular Material.
4
+
5
+ Generates a **complete, production-ready CRUD** (List, Create, Edit, Delete) with:
6
+
7
+ - Angular Material UI (table, paginator, sort, dialogs, snackbar, forms)
8
+ - Reactive Forms with validation
9
+ - REST HTTP service
10
+ - Auto-detects Angular 14+ (Module-based) or Angular 17+ (Standalone)
11
+ - **Safe to uninstall** — generated code has zero dependency on this package
12
+
13
+ ---
14
+
15
+ ## Requirements
16
+
17
+ - Angular 14+
18
+ - Angular Material installed (`ng add @angular/material`)
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install --save-dev ng-crud
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ ng generate ng-crud:crud <name> --fields="field1:type,field2:type" --apiUrl="http://localhost:3000"
34
+ ```
35
+
36
+ ### Options
37
+
38
+ | Option | Type | Default | Description |
39
+ | ------------ | ------- | ----------------------- | ----------------------------------------------- |
40
+ | `name` | string | _(required)_ | Resource name (e.g. `product`, `user`) |
41
+ | `fields` | string | `name:string` | Comma-separated fields. Append `*` for required |
42
+ | `apiUrl` | string | `http://localhost:3000` | Base REST API URL |
43
+ | `path` | string | _(empty)_ | Sub-path inside `src/app/` |
44
+ | `standalone` | boolean | auto-detected | Force standalone mode (Angular 17+) |
45
+
46
+ ### Field Types
47
+
48
+ | Type | Form Control | Example |
49
+ | --------- | ------------ | ---------------- |
50
+ | `string` | text input | `name:string` |
51
+ | `number` | number input | `price:number` |
52
+ | `boolean` | checkbox | `active:boolean` |
53
+
54
+ Append `*` to mark a field as required: `name*:string`
55
+
56
+ ---
57
+
58
+ ## Examples
59
+
60
+ ```bash
61
+ # Basic
62
+ ng generate ng-crud:crud product
63
+
64
+ # With fields
65
+ ng generate ng-crud:crud product --fields="name*:string,price*:number,active:boolean"
66
+
67
+ # With custom API and path
68
+ ng generate ng-crud:crud order --fields="orderId*:number,status*:string,total:number" --apiUrl="https://api.myapp.com" --path="features"
69
+
70
+ # Force standalone (Angular 17+)
71
+ ng generate ng-crud:crud user --fields="username*:string,email*:string,isAdmin:boolean" --standalone=true
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Generated File Structure
77
+
78
+ ### Angular 14–16 (Module-based)
79
+
80
+ ```
81
+ src/app/<name>/
82
+ ├── <name>.module.ts ← NgModule with routing & all Material imports
83
+ ├── models/
84
+ │ └── <name>.model.ts ← TypeScript interface
85
+ ├── services/
86
+ │ └── <name>.service.ts ← HTTP CRUD service (GET/POST/PUT/DELETE)
87
+ ├── <name>-list/
88
+ │ ├── <name>-list.component.ts
89
+ │ ├── <name>-list.component.html ← Material table, paginator, search
90
+ │ ├── <name>-list.component.scss
91
+ │ └── <name>-delete-dialog.component.ts
92
+ └── <name>-form/
93
+ ├── <name>-form.component.ts ← Create & Edit (shared)
94
+ ├── <name>-form.component.html ← Reactive form with validation
95
+ └── <name>-form.component.scss
96
+ ```
97
+
98
+ ### Angular 17+ (Standalone)
99
+
100
+ Same structure but without `<name>.module.ts`. Each component is standalone with its own imports.
101
+
102
+ ---
103
+
104
+ ## After Generation
105
+
106
+ ### Module-based (Angular 14–16)
107
+
108
+ Add a lazy-loaded route to your `app-routing.module.ts`:
109
+
110
+ ```ts
111
+ {
112
+ path: 'products',
113
+ loadChildren: () => import('./product/product.module').then(m => m.ProductModule)
114
+ }
115
+ ```
116
+
117
+ ### Standalone (Angular 17+)
118
+
119
+ The schematic auto-adds the list route to `app.routes.ts`. Add child routes manually if needed:
120
+
121
+ ```ts
122
+ {
123
+ path: 'products',
124
+ loadComponent: () => import('./product/product-list/product-list.component').then(c => c.ProductListComponent)
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Uninstall
131
+
132
+ The generated code has **zero runtime dependency** on `ng-crud`. Uninstall safely anytime:
133
+
134
+ ```bash
135
+ npm uninstall ng-crud
136
+ ```
137
+
138
+ ---
139
+
140
+ ## License
141
+
142
+ MIT
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
3
+ "schematics": {
4
+ "crud": {
5
+ "description": "Generates a complete CRUD module with Material UI (list, form, service, model, routing).",
6
+ "factory": "./crud/index#crud",
7
+ "schema": "./crud/schema.json"
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,48 @@
1
+ <div class="page-container">
2
+
3
+ <div class="page-header">
4
+ <button mat-icon-button (click)="goBack()">
5
+ <mat-icon>arrow_back</mat-icon>
6
+ </button>
7
+ <h2>{{ isEditMode ? 'Edit' : 'Add' }} <%= classify(name) %></h2>
8
+ </div>
9
+
10
+ <div *ngIf="isLoading" class="spinner-wrap">
11
+ <mat-spinner diameter="40"></mat-spinner>
12
+ </div>
13
+
14
+ <mat-card *ngIf="!isLoading">
15
+ <mat-card-content>
16
+ <form [formGroup]="form" (ngSubmit)="onSubmit()" novalidate>
17
+
18
+ <% fields.forEach(function(field) { %>
19
+ <% if (field.control === 'checkbox') { %>
20
+ <div class="field-row">
21
+ <mat-checkbox formControlName="<%= field.name %>"><%= field.label %></mat-checkbox>
22
+ </div>
23
+ <% } else { %>
24
+ <mat-form-field appearance="outline" class="full-width">
25
+ <mat-label><%= field.label %></mat-label>
26
+ <input matInput type="<%= field.control %>" formControlName="<%= field.name %>" />
27
+ <mat-error *ngIf="form.get('<%= field.name %>')?.invalid && form.get('<%= field.name %>')?.touched">
28
+ <% if (field.required) { %> <span *ngIf="form.get('<%= field.name %>')?.hasError('required')">
29
+ <%= field.label %> is required.
30
+ </span>
31
+ <% } %> </mat-error>
32
+ </mat-form-field>
33
+ <% } %>
34
+ <% }); %>
35
+
36
+ <div class="form-actions">
37
+ <button mat-button type="button" (click)="goBack()" [disabled]="isSaving">Cancel</button>
38
+ <button mat-raised-button color="primary" type="submit" [disabled]="isSaving || form.invalid">
39
+ <mat-spinner *ngIf="isSaving" diameter="18" class="inline-spinner"></mat-spinner>
40
+ {{ isSaving ? 'Saving...' : (isEditMode ? 'Update' : 'Save') }}
41
+ </button>
42
+ </div>
43
+
44
+ </form>
45
+ </mat-card-content>
46
+ </mat-card>
47
+
48
+ </div>
@@ -0,0 +1,46 @@
1
+ .page-container {
2
+ padding: 24px;
3
+ max-width: 600px;
4
+ margin: 0 auto;
5
+ }
6
+
7
+ .page-header {
8
+ display: flex;
9
+ align-items: center;
10
+ gap: 8px;
11
+ margin-bottom: 20px;
12
+
13
+ h2 {
14
+ margin: 0;
15
+ font-size: 22px;
16
+ font-weight: 600;
17
+ }
18
+ }
19
+
20
+ .spinner-wrap {
21
+ display: flex;
22
+ justify-content: center;
23
+ padding: 48px 0;
24
+ }
25
+
26
+ .full-width {
27
+ width: 100%;
28
+ margin-bottom: 4px;
29
+ }
30
+
31
+ .field-row {
32
+ margin-bottom: 16px;
33
+ }
34
+
35
+ .form-actions {
36
+ display: flex;
37
+ justify-content: flex-end;
38
+ gap: 8px;
39
+ margin-top: 8px;
40
+ }
41
+
42
+ .inline-spinner {
43
+ display: inline-block;
44
+ margin-right: 6px;
45
+ vertical-align: middle;
46
+ }
@@ -0,0 +1,121 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
3
+ import { ActivatedRoute, Router } from '@angular/router';
4
+ import { MatSnackBar } from '@angular/material/snack-bar';
5
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
6
+ import { <%= classify(name) %>Service } from '../../services/<%= dasherize(name) %>.service';
7
+
8
+ @Component({
9
+ selector: 'app-<%= dasherize(name) %>-form',
10
+ templateUrl: './<%= dasherize(name) %>-form.component.html',
11
+ styleUrls: ['./<%= dasherize(name) %>-form.component.scss'],
12
+ })
13
+ export class <%= classify(name) %>FormComponent implements OnInit {
14
+ form!: FormGroup;
15
+ isEditMode = false;
16
+ isLoading = false;
17
+ isSaving = false;
18
+ itemId: number | null = null;
19
+
20
+ constructor(
21
+ private fb: FormBuilder,
22
+ private service: <%= classify(name) %>Service,
23
+ private router: Router,
24
+ private route: ActivatedRoute,
25
+ private snackBar: MatSnackBar
26
+ ) {}
27
+
28
+ ngOnInit(): void {
29
+ this.buildForm();
30
+ const id = this.route.snapshot.paramMap.get('id');
31
+ if (id) {
32
+ this.isEditMode = true;
33
+ this.itemId = +id;
34
+ this.loadItem(this.itemId);
35
+ }
36
+ }
37
+
38
+ buildForm(): void {
39
+ this.form = this.fb.group({<% fields.forEach(function(field, i) { %>
40
+ <%= field.name %>: [<%= field.type === 'boolean' ? 'false' : field.type === 'number' ? 'null' : "''" %><% if (field.required) { %>, [Validators.required]<% } %>]<% if (i < fields.length - 1) { %>,<% } %><% }); %>
41
+ });
42
+ }
43
+
44
+ loadItem(id: number): void {
45
+ this.isLoading = true;
46
+ this.service.getById(id).subscribe({
47
+ next: (item) => {
48
+ this.form.patchValue(item);
49
+ this.isLoading = false;
50
+ },
51
+ error: (error) => {
52
+ let errorMessage = 'Failed to load item.';
53
+ if (error.status === 404) {
54
+ errorMessage = '<%= classify(name) %> not found.';
55
+ this.router.navigate(['/<%= dasherize(name) %>']);
56
+ } else if (error.status === 500) {
57
+ errorMessage = 'Server error. Please try again later.';
58
+ }
59
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
60
+ this.isLoading = false;
61
+ },
62
+ });
63
+ }
64
+
65
+ onSubmit(): void {
66
+ if (this.form.invalid) {
67
+ this.form.markAllAsTouched();
68
+ this.showValidationErrors();
69
+ return;
70
+ }
71
+ this.isSaving = true;
72
+ const value: <%= classify(name) %> = this.form.value;
73
+ const request = this.isEditMode
74
+ ? this.service.update(this.itemId!, value)
75
+ : this.service.create(value);
76
+
77
+ request.subscribe({
78
+ next: () => {
79
+ this.snackBar.open(
80
+ `<%= classify(name) %> ${this.isEditMode ? 'updated' : 'created'} successfully.`,
81
+ 'Close',
82
+ { duration: 3000 }
83
+ );
84
+ this.isSaving = false;
85
+ this.goBack();
86
+ },
87
+ error: (error) => {
88
+ let errorMessage = 'Save failed. Please try again.';
89
+ if (error.status === 400 && error.error?.message) {
90
+ errorMessage = error.error.message;
91
+ } else if (error.status === 404) {
92
+ errorMessage = '<%= classify(name) %> not found.';
93
+ } else if (error.status === 500) {
94
+ errorMessage = 'Server error. Please try again later.';
95
+ }
96
+ this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
97
+ this.isSaving = false;
98
+ },
99
+ });
100
+ }
101
+
102
+ private showValidationErrors(): void {
103
+ const errors: string[] = [];
104
+ Object.keys(this.form.controls).forEach(key => {
105
+ const control = this.form.get(key);
106
+ if (control?.invalid && control.errors) {
107
+ if (control.errors['required']) {
108
+ const fieldName = key.charAt(0).toUpperCase() + key.slice(1);
109
+ errors.push(`${fieldName} is required`);
110
+ }
111
+ }
112
+ });
113
+ if (errors.length > 0) {
114
+ this.snackBar.open(errors.join(', '), 'Close', { duration: 4000 });
115
+ }
116
+ }
117
+
118
+ goBack(): void {
119
+ this.router.navigate(['..'], { relativeTo: this.route });
120
+ }
121
+ }
@@ -0,0 +1,27 @@
1
+ import { Component, Inject } from '@angular/core';
2
+ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
3
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
4
+
5
+ @Component({
6
+ selector: 'app-<%= dasherize(name) %>-delete-dialog',
7
+ template: `
8
+ <h2 mat-dialog-title>Delete <%= classify(name) %></h2>
9
+ <mat-dialog-content>
10
+ Are you sure you want to delete this record? This cannot be undone.
11
+ </mat-dialog-content>
12
+ <mat-dialog-actions align="end">
13
+ <button mat-button (click)="close(false)">Cancel</button>
14
+ <button mat-raised-button color="warn" (click)="close(true)">Delete</button>
15
+ </mat-dialog-actions>
16
+ `,
17
+ })
18
+ export class <%= classify(name) %>DeleteDialogComponent {
19
+ constructor(
20
+ public dialogRef: MatDialogRef<<%= classify(name) %>DeleteDialogComponent>,
21
+ @Inject(MAT_DIALOG_DATA) public data: <%= classify(name) %>
22
+ ) {}
23
+
24
+ close(result: boolean): void {
25
+ this.dialogRef.close(result);
26
+ }
27
+ }
@@ -0,0 +1,75 @@
1
+ <div class="page-container">
2
+
3
+ <div class="page-header">
4
+ <h2><%= classify(name) %> List</h2>
5
+ <button mat-raised-button color="primary" (click)="onCreate()">
6
+ <mat-icon>add</mat-icon> Add <%= classify(name) %>
7
+ </button>
8
+ </div>
9
+
10
+ <mat-card>
11
+ <mat-card-content>
12
+
13
+ <mat-form-field appearance="outline" class="search-box">
14
+ <mat-label>Search</mat-label>
15
+ <mat-icon matPrefix>search</mat-icon>
16
+ <input matInput (keyup)="applyFilter($event)" placeholder="Search..." />
17
+ </mat-form-field>
18
+
19
+ <div *ngIf="isLoading" class="spinner-wrap">
20
+ <mat-spinner diameter="40"></mat-spinner>
21
+ </div>
22
+
23
+ <div [hidden]="isLoading">
24
+ <table mat-table [dataSource]="dataSource" matSort>
25
+
26
+ <ng-container matColumnDef="id">
27
+ <th mat-header-cell *matHeaderCellDef mat-sort-header>#</th>
28
+ <td mat-cell *matCellDef="let row">{{ row.id }}</td>
29
+ </ng-container>
30
+
31
+ <% fields.forEach(function(field) { %>
32
+ <ng-container matColumnDef="<%= field.name %>">
33
+ <th mat-header-cell *matHeaderCellDef mat-sort-header><%= field.label %></th>
34
+ <td mat-cell *matCellDef="let row">
35
+ <% if (field.type === 'boolean') { %>
36
+ <mat-chip [color]="row.<%= field.name %> ? 'primary' : ''" selected>
37
+ {{ row.<%= field.name %> ? 'Yes' : 'No' }}
38
+ </mat-chip>
39
+ <% } else { %>
40
+ {{ row.<%= field.name %> }}
41
+ <% } %>
42
+ </td>
43
+ </ng-container>
44
+ <% }); %>
45
+
46
+ <ng-container matColumnDef="actions">
47
+ <th mat-header-cell *matHeaderCellDef>Actions</th>
48
+ <td mat-cell *matCellDef="let row">
49
+ <button mat-icon-button color="primary" (click)="onEdit(row)" matTooltip="Edit">
50
+ <mat-icon>edit</mat-icon>
51
+ </button>
52
+ <button mat-icon-button color="warn" (click)="onDelete(row)" matTooltip="Delete">
53
+ <mat-icon>delete</mat-icon>
54
+ </button>
55
+ </td>
56
+ </ng-container>
57
+
58
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
59
+ <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
60
+
61
+ <tr class="mat-row" *matNoDataRow>
62
+ <td class="mat-cell no-data" [attr.colspan]="displayedColumns.length">
63
+ No records found.
64
+ </td>
65
+ </tr>
66
+
67
+ </table>
68
+
69
+ <mat-paginator [pageSizeOptions]="[5, 10, 25]" showFirstLastButtons></mat-paginator>
70
+ </div>
71
+
72
+ </mat-card-content>
73
+ </mat-card>
74
+
75
+ </div>
@@ -0,0 +1,40 @@
1
+ .page-container {
2
+ padding: 24px;
3
+ max-width: 1100px;
4
+ margin: 0 auto;
5
+ }
6
+
7
+ .page-header {
8
+ display: flex;
9
+ justify-content: space-between;
10
+ align-items: center;
11
+ margin-bottom: 16px;
12
+
13
+ h2 {
14
+ margin: 0;
15
+ font-size: 22px;
16
+ font-weight: 600;
17
+ }
18
+ }
19
+
20
+ .search-box {
21
+ width: 100%;
22
+ max-width: 360px;
23
+ margin-bottom: 16px;
24
+ }
25
+
26
+ .spinner-wrap {
27
+ display: flex;
28
+ justify-content: center;
29
+ padding: 48px 0;
30
+ }
31
+
32
+ table {
33
+ width: 100%;
34
+ }
35
+
36
+ .no-data {
37
+ text-align: center;
38
+ padding: 32px;
39
+ color: #888;
40
+ }
@@ -0,0 +1,95 @@
1
+ import { Component, OnInit, ViewChild } from '@angular/core';
2
+ import { MatPaginator } from '@angular/material/paginator';
3
+ import { MatSort } from '@angular/material/sort';
4
+ import { MatTableDataSource } from '@angular/material/table';
5
+ import { MatDialog } from '@angular/material/dialog';
6
+ import { MatSnackBar } from '@angular/material/snack-bar';
7
+ import { Router, ActivatedRoute } from '@angular/router';
8
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
9
+ import { <%= classify(name) %>Service } from '../../services/<%= dasherize(name) %>.service';
10
+ import { <%= classify(name) %>DeleteDialogComponent } from './<%= dasherize(name) %>-delete-dialog.component';
11
+
12
+ @Component({
13
+ selector: 'app-<%= dasherize(name) %>-list',
14
+ templateUrl: './<%= dasherize(name) %>-list.component.html',
15
+ styleUrls: ['./<%= dasherize(name) %>-list.component.scss'],
16
+ })
17
+ export class <%= classify(name) %>ListComponent implements OnInit {
18
+ displayedColumns: string[] = ['id', <% fields.forEach(function(f, i) { %>'<%= f.name %>'<% if (i < fields.length - 1) { %>, <% } %><% }); %>, 'actions'];
19
+ dataSource = new MatTableDataSource<<%= classify(name) %>>();
20
+ isLoading = true;
21
+
22
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
23
+ @ViewChild(MatSort) sort!: MatSort;
24
+
25
+ constructor(
26
+ private service: <%= classify(name) %>Service,
27
+ private router: Router,
28
+ private route: ActivatedRoute,
29
+ private dialog: MatDialog,
30
+ private snackBar: MatSnackBar
31
+ ) {}
32
+
33
+ ngOnInit(): void {
34
+ this.loadData();
35
+ }
36
+
37
+ loadData(): void {
38
+ this.isLoading = true;
39
+ this.service.getAll().subscribe({
40
+ next: (data) => {
41
+ this.dataSource.data = data;
42
+ this.dataSource.paginator = this.paginator;
43
+ this.dataSource.sort = this.sort;
44
+ this.isLoading = false;
45
+ },
46
+ error: (error) => {
47
+ let errorMessage = 'Failed to load data.';
48
+ if (error.status === 500) {
49
+ errorMessage = 'Server error. Please try again later.';
50
+ } else if (error.status === 0) {
51
+ errorMessage = 'Unable to connect to server. Please check your connection.';
52
+ }
53
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
54
+ this.isLoading = false;
55
+ },
56
+ });
57
+ }
58
+
59
+ applyFilter(event: Event): void {
60
+ const filterValue = (event.target as HTMLInputElement).value;
61
+ this.dataSource.filter = filterValue.trim().toLowerCase();
62
+ if (this.dataSource.paginator) this.dataSource.paginator.firstPage();
63
+ }
64
+
65
+ onCreate(): void {
66
+ this.router.navigate(['new'], { relativeTo: this.route });
67
+ }
68
+
69
+ onEdit(item: <%= classify(name) %>): void {
70
+ this.router.navigate([item.id, 'edit'], { relativeTo: this.route });
71
+ }
72
+
73
+ onDelete(item: <%= classify(name) %>): void {
74
+ const ref = this.dialog.open(<%= classify(name) %>DeleteDialogComponent, { data: item, width: '400px' });
75
+ ref.afterClosed().subscribe((confirmed) => {
76
+ if (confirmed) {
77
+ this.service.delete(item.id!).subscribe({
78
+ next: () => {
79
+ this.snackBar.open('<%= classify(name) %> deleted successfully.', 'Close', { duration: 3000 });
80
+ this.loadData();
81
+ },
82
+ error: (error) => {
83
+ let errorMessage = 'Delete failed.';
84
+ if (error.status === 404) {
85
+ errorMessage = '<%= classify(name) %> not found or already deleted.';
86
+ } else if (error.status === 500) {
87
+ errorMessage = 'Server error. Please try again later.';
88
+ }
89
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
90
+ },
91
+ });
92
+ }
93
+ });
94
+ }
95
+ }
@@ -0,0 +1,73 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ReactiveFormsModule } from '@angular/forms';
4
+ import { HttpClientModule } from '@angular/common/http';
5
+ import { RouterModule, Routes } from '@angular/router';
6
+ import { MatTableModule } from '@angular/material/table';
7
+ import { MatPaginatorModule } from '@angular/material/paginator';
8
+ import { MatSortModule } from '@angular/material/sort';
9
+ import { MatInputModule } from '@angular/material/input';
10
+ import { MatButtonModule } from '@angular/material/button';
11
+ import { MatIconModule } from '@angular/material/icon';
12
+ import { MatDialogModule } from '@angular/material/dialog';
13
+ import { MatFormFieldModule } from '@angular/material/form-field';
14
+ import { MatCheckboxModule } from '@angular/material/checkbox';
15
+ import { MatSnackBarModule } from '@angular/material/snack-bar';
16
+ import { MatCardModule } from '@angular/material/card';
17
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
18
+ import { MatTooltipModule } from '@angular/material/tooltip';
19
+ import { MatChipsModule } from '@angular/material/chips';
20
+ import { <%= classify(name) %>ListComponent } from './<%= dasherize(name) %>-list.component';
21
+ import { <%= classify(name) %>DeleteDialogComponent } from './<%= dasherize(name) %>-delete-dialog.component';
22
+ import { <%= classify(name) %>FormComponent } from '../<%= dasherize(name) %>-form/<%= dasherize(name) %>-form.component';
23
+
24
+ const routes: Routes = [
25
+ {
26
+ path: '',
27
+ component: <%= classify(name) %>ListComponent,
28
+ title: '<%= classify(name) %> List'
29
+ },
30
+ {
31
+ path: 'new',
32
+ component: <%= classify(name) %>FormComponent,
33
+ title: 'New <%= classify(name) %>'
34
+ },
35
+ {
36
+ path: ':id/edit',
37
+ component: <%= classify(name) %>FormComponent,
38
+ title: 'Edit <%= classify(name) %>'
39
+ },
40
+ {
41
+ path: '**',
42
+ redirectTo: ''
43
+ }
44
+ ];
45
+
46
+ @NgModule({
47
+ declarations: [
48
+ <%= classify(name) %>ListComponent,
49
+ <%= classify(name) %>FormComponent,
50
+ <%= classify(name) %>DeleteDialogComponent,
51
+ ],
52
+ imports: [
53
+ CommonModule,
54
+ ReactiveFormsModule,
55
+ HttpClientModule,
56
+ RouterModule.forChild(routes),
57
+ MatTableModule,
58
+ MatPaginatorModule,
59
+ MatSortModule,
60
+ MatInputModule,
61
+ MatButtonModule,
62
+ MatIconModule,
63
+ MatDialogModule,
64
+ MatFormFieldModule,
65
+ MatCheckboxModule,
66
+ MatSnackBarModule,
67
+ MatCardModule,
68
+ MatProgressSpinnerModule,
69
+ MatTooltipModule,
70
+ MatChipsModule,
71
+ ],
72
+ })
73
+ export class <%= classify(name) %>Module {}
@@ -0,0 +1,4 @@
1
+ export interface <%= classify(name) %> {
2
+ id?: number;<% fields.forEach(function(field) { %>
3
+ <%= field.name %>: <%= field.type %>;<% }); %>
4
+ }