@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
@@ -0,0 +1,42 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
+ import { Observable, throwError } from 'rxjs';
4
+ import { catchError } from 'rxjs/operators';
5
+ import { <%= classify(name) %> } from '../models/<%= dasherize(name) %>.model';
6
+
7
+ @Injectable({ providedIn: 'root' })
8
+ export class <%= classify(name) %>Service {
9
+ private apiUrl = '<%= apiUrl %>/<%= dasherize(name) %>s';
10
+
11
+ constructor(private http: HttpClient) {}
12
+
13
+ getAll(): Observable<<%= classify(name) %>[]> {
14
+ return this.http.get<<%= classify(name) %>[]>(this.apiUrl)
15
+ .pipe(catchError(this.handleError));
16
+ }
17
+
18
+ getById(id: number): Observable<<%= classify(name) %>> {
19
+ return this.http.get<<%= classify(name) %>>(`${this.apiUrl}/${id}`)
20
+ .pipe(catchError(this.handleError));
21
+ }
22
+
23
+ create(item: <%= classify(name) %>): Observable<<%= classify(name) %>> {
24
+ return this.http.post<<%= classify(name) %>>(this.apiUrl, item)
25
+ .pipe(catchError(this.handleError));
26
+ }
27
+
28
+ update(id: number, item: <%= classify(name) %>): Observable<<%= classify(name) %>> {
29
+ return this.http.put<<%= classify(name) %>>(`${this.apiUrl}/${id}`, item)
30
+ .pipe(catchError(this.handleError));
31
+ }
32
+
33
+ delete(id: number): Observable<void> {
34
+ return this.http.delete<void>(`${this.apiUrl}/${id}`)
35
+ .pipe(catchError(this.handleError));
36
+ }
37
+
38
+ private handleError(error: HttpErrorResponse): Observable<never> {
39
+ console.error('HTTP Error:', error);
40
+ return throwError(() => error);
41
+ }
42
+ }
@@ -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,142 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
4
+ import { ActivatedRoute, Router } from '@angular/router';
5
+ import { MatInputModule } from '@angular/material/input';
6
+ import { MatButtonModule } from '@angular/material/button';
7
+ import { MatFormFieldModule } from '@angular/material/form-field';
8
+ import { MatCheckboxModule } from '@angular/material/checkbox';
9
+ import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
10
+ import { MatCardModule } from '@angular/material/card';
11
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
12
+ import { MatIconModule } from '@angular/material/icon';
13
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
14
+ import { <%= classify(name) %>Service } from '../../services/<%= dasherize(name) %>.service';
15
+
16
+ @Component({
17
+ selector: 'app-<%= dasherize(name) %>-form',
18
+ standalone: true,
19
+ imports: [
20
+ CommonModule,
21
+ ReactiveFormsModule,
22
+ MatInputModule,
23
+ MatButtonModule,
24
+ MatFormFieldModule,
25
+ MatCheckboxModule,
26
+ MatSnackBarModule,
27
+ MatCardModule,
28
+ MatProgressSpinnerModule,
29
+ MatIconModule,
30
+ ],
31
+ templateUrl: './<%= dasherize(name) %>-form.component.html',
32
+ styleUrls: ['./<%= dasherize(name) %>-form.component.scss'],
33
+ })
34
+ export class <%= classify(name) %>FormComponent implements OnInit {
35
+ form!: FormGroup;
36
+ isEditMode = false;
37
+ isLoading = false;
38
+ isSaving = false;
39
+ itemId: number | null = null;
40
+
41
+ constructor(
42
+ private fb: FormBuilder,
43
+ private service: <%= classify(name) %>Service,
44
+ private router: Router,
45
+ private route: ActivatedRoute,
46
+ private snackBar: MatSnackBar
47
+ ) {}
48
+
49
+ ngOnInit(): void {
50
+ this.buildForm();
51
+ const id = this.route.snapshot.paramMap.get('id');
52
+ if (id) {
53
+ this.isEditMode = true;
54
+ this.itemId = +id;
55
+ this.loadItem(this.itemId);
56
+ }
57
+ }
58
+
59
+ buildForm(): void {
60
+ this.form = this.fb.group({<% fields.forEach(function(field, i) { %>
61
+ <%= field.name %>: [<%= field.type === 'boolean' ? 'false' : field.type === 'number' ? 'null' : "''" %><% if (field.required) { %>, [Validators.required]<% } %>]<% if (i < fields.length - 1) { %>,<% } %><% }); %>
62
+ });
63
+ }
64
+
65
+ loadItem(id: number): void {
66
+ this.isLoading = true;
67
+ this.service.getById(id).subscribe({
68
+ next: (item) => {
69
+ this.form.patchValue(item);
70
+ this.isLoading = false;
71
+ },
72
+ error: (error) => {
73
+ let errorMessage = 'Failed to load item.';
74
+ if (error.status === 404) {
75
+ errorMessage = '<%= classify(name) %> not found.';
76
+ this.router.navigate(['/<%= dasherize(name) %>']);
77
+ } else if (error.status === 500) {
78
+ errorMessage = 'Server error. Please try again later.';
79
+ }
80
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
81
+ this.isLoading = false;
82
+ },
83
+ });
84
+ }
85
+
86
+ onSubmit(): void {
87
+ if (this.form.invalid) {
88
+ this.form.markAllAsTouched();
89
+ this.showValidationErrors();
90
+ return;
91
+ }
92
+ this.isSaving = true;
93
+ const value: <%= classify(name) %> = this.form.value;
94
+ const request = this.isEditMode
95
+ ? this.service.update(this.itemId!, value)
96
+ : this.service.create(value);
97
+
98
+ request.subscribe({
99
+ next: () => {
100
+ this.snackBar.open(
101
+ `<%= classify(name) %> ${this.isEditMode ? 'updated' : 'created'} successfully.`,
102
+ 'Close',
103
+ { duration: 3000 }
104
+ );
105
+ this.isSaving = false;
106
+ this.goBack();
107
+ },
108
+ error: (error) => {
109
+ let errorMessage = 'Save failed. Please try again.';
110
+ if (error.status === 400 && error.error?.message) {
111
+ errorMessage = error.error.message;
112
+ } else if (error.status === 404) {
113
+ errorMessage = '<%= classify(name) %> not found.';
114
+ } else if (error.status === 500) {
115
+ errorMessage = 'Server error. Please try again later.';
116
+ }
117
+ this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
118
+ this.isSaving = false;
119
+ },
120
+ });
121
+ }
122
+
123
+ private showValidationErrors(): void {
124
+ const errors: string[] = [];
125
+ Object.keys(this.form.controls).forEach(key => {
126
+ const control = this.form.get(key);
127
+ if (control?.invalid && control.errors) {
128
+ if (control.errors['required']) {
129
+ const fieldName = key.charAt(0).toUpperCase() + key.slice(1);
130
+ errors.push(`${fieldName} is required`);
131
+ }
132
+ }
133
+ });
134
+ if (errors.length > 0) {
135
+ this.snackBar.open(errors.join(', '), 'Close', { duration: 4000 });
136
+ }
137
+ }
138
+
139
+ goBack(): void {
140
+ this.router.navigate(['..'], { relativeTo: this.route });
141
+ }
142
+ }
@@ -0,0 +1,30 @@
1
+ import { Component, Inject } from '@angular/core';
2
+ import { MatButtonModule } from '@angular/material/button';
3
+ import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
4
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
5
+
6
+ @Component({
7
+ selector: 'app-<%= dasherize(name) %>-delete-dialog',
8
+ standalone: true,
9
+ imports: [MatDialogModule, MatButtonModule],
10
+ template: `
11
+ <h2 mat-dialog-title>Delete <%= classify(name) %></h2>
12
+ <mat-dialog-content>
13
+ Are you sure you want to delete this record? This cannot be undone.
14
+ </mat-dialog-content>
15
+ <mat-dialog-actions align="end">
16
+ <button mat-button (click)="close(false)">Cancel</button>
17
+ <button mat-raised-button color="warn" (click)="close(true)">Delete</button>
18
+ </mat-dialog-actions>
19
+ `,
20
+ })
21
+ export class <%= classify(name) %>DeleteDialogComponent {
22
+ constructor(
23
+ public dialogRef: MatDialogRef<<%= classify(name) %>DeleteDialogComponent>,
24
+ @Inject(MAT_DIALOG_DATA) public data: <%= classify(name) %>
25
+ ) {}
26
+
27
+ close(result: boolean): void {
28
+ this.dialogRef.close(result);
29
+ }
30
+ }
@@ -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,121 @@
1
+ import { Component, OnInit, ViewChild } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { Router, ActivatedRoute } from '@angular/router';
4
+ import { MatTableModule, MatTableDataSource } from '@angular/material/table';
5
+ import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
6
+ import { MatSortModule, MatSort } from '@angular/material/sort';
7
+ import { MatInputModule } from '@angular/material/input';
8
+ import { MatButtonModule } from '@angular/material/button';
9
+ import { MatIconModule } from '@angular/material/icon';
10
+ import { MatDialogModule, MatDialog } from '@angular/material/dialog';
11
+ import { MatFormFieldModule } from '@angular/material/form-field';
12
+ import { MatSnackBarModule, MatSnackBar } from '@angular/material/snack-bar';
13
+ import { MatCardModule } from '@angular/material/card';
14
+ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
15
+ import { MatTooltipModule } from '@angular/material/tooltip';
16
+ import { MatChipsModule } from '@angular/material/chips';
17
+ import { <%= classify(name) %> } from '../../models/<%= dasherize(name) %>.model';
18
+ import { <%= classify(name) %>Service } from '../../services/<%= dasherize(name) %>.service';
19
+ import { <%= classify(name) %>DeleteDialogComponent } from './<%= dasherize(name) %>-delete-dialog.component';
20
+
21
+ @Component({
22
+ selector: 'app-<%= dasherize(name) %>-list',
23
+ standalone: true,
24
+ imports: [
25
+ CommonModule,
26
+ MatTableModule,
27
+ MatPaginatorModule,
28
+ MatSortModule,
29
+ MatInputModule,
30
+ MatButtonModule,
31
+ MatIconModule,
32
+ MatDialogModule,
33
+ MatFormFieldModule,
34
+ MatSnackBarModule,
35
+ MatCardModule,
36
+ MatProgressSpinnerModule,
37
+ MatTooltipModule,
38
+ MatChipsModule,
39
+ ],
40
+ templateUrl: './<%= dasherize(name) %>-list.component.html',
41
+ styleUrls: ['./<%= dasherize(name) %>-list.component.scss'],
42
+ })
43
+ export class <%= classify(name) %>ListComponent implements OnInit {
44
+ displayedColumns: string[] = ['id', <% fields.forEach(function(f, i) { %>'<%= f.name %>'<% if (i < fields.length - 1) { %>, <% } %><% }); %>, 'actions'];
45
+ dataSource = new MatTableDataSource<<%= classify(name) %>>();
46
+ isLoading = true;
47
+
48
+ @ViewChild(MatPaginator) paginator!: MatPaginator;
49
+ @ViewChild(MatSort) sort!: MatSort;
50
+
51
+ constructor(
52
+ private service: <%= classify(name) %>Service,
53
+ private router: Router,
54
+ private route: ActivatedRoute,
55
+ private dialog: MatDialog,
56
+ private snackBar: MatSnackBar
57
+ ) {}
58
+
59
+ ngOnInit(): void {
60
+ this.loadData();
61
+ }
62
+
63
+ loadData(): void {
64
+ this.isLoading = true;
65
+ this.service.getAll().subscribe({
66
+ next: (data) => {
67
+ this.dataSource.data = data;
68
+ this.dataSource.paginator = this.paginator;
69
+ this.dataSource.sort = this.sort;
70
+ this.isLoading = false;
71
+ },
72
+ error: (error) => {
73
+ let errorMessage = 'Failed to load data.';
74
+ if (error.status === 500) {
75
+ errorMessage = 'Server error. Please try again later.';
76
+ } else if (error.status === 0) {
77
+ errorMessage = 'Unable to connect to server. Please check your connection.';
78
+ }
79
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
80
+ this.isLoading = false;
81
+ },
82
+ });
83
+ }
84
+
85
+ applyFilter(event: Event): void {
86
+ const filterValue = (event.target as HTMLInputElement).value;
87
+ this.dataSource.filter = filterValue.trim().toLowerCase();
88
+ if (this.dataSource.paginator) this.dataSource.paginator.firstPage();
89
+ }
90
+
91
+ onCreate(): void {
92
+ this.router.navigate(['new'], { relativeTo: this.route });
93
+ }
94
+
95
+ onEdit(item: <%= classify(name) %>): void {
96
+ this.router.navigate([item.id, 'edit'], { relativeTo: this.route });
97
+ }
98
+
99
+ onDelete(item: <%= classify(name) %>): void {
100
+ const ref = this.dialog.open(<%= classify(name) %>DeleteDialogComponent, { data: item, width: '400px' });
101
+ ref.afterClosed().subscribe((confirmed) => {
102
+ if (confirmed) {
103
+ this.service.delete(item.id!).subscribe({
104
+ next: () => {
105
+ this.snackBar.open('<%= classify(name) %> deleted successfully.', 'Close', { duration: 3000 });
106
+ this.loadData();
107
+ },
108
+ error: (error) => {
109
+ let errorMessage = 'Delete failed.';
110
+ if (error.status === 404) {
111
+ errorMessage = '<%= classify(name) %> not found or already deleted.';
112
+ } else if (error.status === 500) {
113
+ errorMessage = 'Server error. Please try again later.';
114
+ }
115
+ this.snackBar.open(errorMessage, 'Close', { duration: 4000 });
116
+ },
117
+ });
118
+ }
119
+ });
120
+ }
121
+ }
@@ -0,0 +1,4 @@
1
+ export interface <%= classify(name) %> {
2
+ id?: number;<% fields.forEach(function(field) { %>
3
+ <%= field.name %>: <%= field.type %>;<% }); %>
4
+ }
@@ -0,0 +1,42 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
+ import { Observable, throwError } from 'rxjs';
4
+ import { catchError } from 'rxjs/operators';
5
+ import { <%= classify(name) %> } from '../models/<%= dasherize(name) %>.model';
6
+
7
+ @Injectable({ providedIn: 'root' })
8
+ export class <%= classify(name) %>Service {
9
+ private apiUrl = '<%= apiUrl %>/<%= dasherize(name) %>s';
10
+
11
+ constructor(private http: HttpClient) {}
12
+
13
+ getAll(): Observable<<%= classify(name) %>[]> {
14
+ return this.http.get<<%= classify(name) %>[]>(this.apiUrl)
15
+ .pipe(catchError(this.handleError));
16
+ }
17
+
18
+ getById(id: number): Observable<<%= classify(name) %>> {
19
+ return this.http.get<<%= classify(name) %>>(`${this.apiUrl}/${id}`)
20
+ .pipe(catchError(this.handleError));
21
+ }
22
+
23
+ create(item: <%= classify(name) %>): Observable<<%= classify(name) %>> {
24
+ return this.http.post<<%= classify(name) %>>(this.apiUrl, item)
25
+ .pipe(catchError(this.handleError));
26
+ }
27
+
28
+ update(id: number, item: <%= classify(name) %>): Observable<<%= classify(name) %>> {
29
+ return this.http.put<<%= classify(name) %>>(`${this.apiUrl}/${id}`, item)
30
+ .pipe(catchError(this.handleError));
31
+ }
32
+
33
+ delete(id: number): Observable<void> {
34
+ return this.http.delete<void>(`${this.apiUrl}/${id}`)
35
+ .pipe(catchError(this.handleError));
36
+ }
37
+
38
+ private handleError(error: HttpErrorResponse): Observable<never> {
39
+ console.error('HTTP Error:', error);
40
+ return throwError(() => error);
41
+ }
42
+ }
@@ -0,0 +1,3 @@
1
+ import { Rule } from '@angular-devkit/schematics';
2
+ import { Schema } from './schema';
3
+ export declare function crud(options: Schema): Rule;