@alicanyucelankara06/generic-service 1.0.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.
- package/README.md +202 -0
- package/dist/README.md +202 -0
- package/dist/fesm2022/acy-generic-service.mjs +336 -0
- package/dist/fesm2022/acy-generic-service.mjs.map +1 -0
- package/dist/types/acy-generic-service.d.ts +238 -0
- package/ng-package.json +7 -0
- package/package.json +27 -0
- package/src/lib/generic-service.config.ts +50 -0
- package/src/lib/generic.service.ts +392 -0
- package/src/lib/models.ts +49 -0
- package/src/public-api.ts +16 -0
- package/tsconfig.json +23 -0
- package/tsconfig.lib.json +9 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
2
|
+
import { InjectionToken, inject, signal, computed } from '@angular/core';
|
|
3
|
+
import { tap, catchError, finalize, throwError } from 'rxjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Konfigürasyon için InjectionToken
|
|
7
|
+
*/
|
|
8
|
+
const GENERIC_SERVICE_CONFIG = new InjectionToken('GENERIC_SERVICE_CONFIG');
|
|
9
|
+
/**
|
|
10
|
+
* Konfigürasyonu provide etmek için yardımcı fonksiyon
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // app.config.ts
|
|
15
|
+
* export const appConfig: ApplicationConfig = {
|
|
16
|
+
* providers: [
|
|
17
|
+
* provideGenericServiceConfig({
|
|
18
|
+
* baseUrl: 'https://api.example.com',
|
|
19
|
+
* defaultPageSize: 20,
|
|
20
|
+
* enableLogging: true,
|
|
21
|
+
* }),
|
|
22
|
+
* ],
|
|
23
|
+
* };
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function provideGenericServiceConfig(config) {
|
|
27
|
+
return {
|
|
28
|
+
provide: GENERIC_SERVICE_CONFIG,
|
|
29
|
+
useValue: {
|
|
30
|
+
defaultPageSize: 10,
|
|
31
|
+
enableLogging: false,
|
|
32
|
+
...config,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Angular 21 Generic CRUD Service
|
|
39
|
+
*
|
|
40
|
+
* Tüm entity servisleri için temel sınıf.
|
|
41
|
+
* HTTP CRUD operasyonları, pagination, filtreleme, hata yönetimi ve
|
|
42
|
+
* reactive state (signals) desteği sağlar.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* // user.service.ts
|
|
47
|
+
* @Injectable({ providedIn: 'root' })
|
|
48
|
+
* export class UserService extends GenericService<User> {
|
|
49
|
+
* constructor() {
|
|
50
|
+
* super('users'); // endpoint: /api/users
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* // component içinde kullanım
|
|
55
|
+
* export class UserListComponent {
|
|
56
|
+
* private userService = inject(UserService);
|
|
57
|
+
*
|
|
58
|
+
* users = this.userService.items;
|
|
59
|
+
* loading = this.userService.loading;
|
|
60
|
+
* error = this.userService.error;
|
|
61
|
+
* totalCount = this.userService.totalCount;
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
class GenericService {
|
|
66
|
+
endpoint;
|
|
67
|
+
// Angular 21 inject() ile DI
|
|
68
|
+
http = inject(HttpClient);
|
|
69
|
+
config = inject(GENERIC_SERVICE_CONFIG);
|
|
70
|
+
// Reactive state — Angular Signals
|
|
71
|
+
_items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
|
|
72
|
+
_selectedItem = signal(null, ...(ngDevMode ? [{ debugName: "_selectedItem" }] : /* istanbul ignore next */ []));
|
|
73
|
+
_loading = signal(false, ...(ngDevMode ? [{ debugName: "_loading" }] : /* istanbul ignore next */ []));
|
|
74
|
+
_error = signal(null, ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
|
|
75
|
+
_totalCount = signal(0, ...(ngDevMode ? [{ debugName: "_totalCount" }] : /* istanbul ignore next */ []));
|
|
76
|
+
_currentPage = signal(1, ...(ngDevMode ? [{ debugName: "_currentPage" }] : /* istanbul ignore next */ []));
|
|
77
|
+
_pageSize;
|
|
78
|
+
/** Mevcut entity listesi (readonly signal) */
|
|
79
|
+
items = this._items.asReadonly();
|
|
80
|
+
/** Seçili entity (readonly signal) */
|
|
81
|
+
selectedItem = this._selectedItem.asReadonly();
|
|
82
|
+
/** Yüklenme durumu */
|
|
83
|
+
loading = this._loading.asReadonly();
|
|
84
|
+
/** Son hata */
|
|
85
|
+
error = this._error.asReadonly();
|
|
86
|
+
/** Toplam kayıt sayısı */
|
|
87
|
+
totalCount = this._totalCount.asReadonly();
|
|
88
|
+
/** Mevcut sayfa */
|
|
89
|
+
currentPage = this._currentPage.asReadonly();
|
|
90
|
+
/** Sayfa boyutu */
|
|
91
|
+
pageSize;
|
|
92
|
+
/** Toplam sayfa sayısı (computed) */
|
|
93
|
+
totalPages = computed(() => Math.ceil(this._totalCount() / this._pageSize()), ...(ngDevMode ? [{ debugName: "totalPages" }] : /* istanbul ignore next */ []));
|
|
94
|
+
/** Veri var mı? (computed) */
|
|
95
|
+
hasData = computed(() => this._items().length > 0, ...(ngDevMode ? [{ debugName: "hasData" }] : /* istanbul ignore next */ []));
|
|
96
|
+
/** Tam API URL */
|
|
97
|
+
apiUrl;
|
|
98
|
+
/**
|
|
99
|
+
* @param endpoint - API endpoint yolu (örn: 'users', 'products')
|
|
100
|
+
*/
|
|
101
|
+
constructor(endpoint) {
|
|
102
|
+
this.endpoint = endpoint;
|
|
103
|
+
this._pageSize = signal(this.config.defaultPageSize ?? 10, ...(ngDevMode ? [{ debugName: "_pageSize" }] : /* istanbul ignore next */ []));
|
|
104
|
+
this.pageSize = computed(() => this._pageSize(), ...(ngDevMode ? [{ debugName: "pageSize" }] : /* istanbul ignore next */ []));
|
|
105
|
+
this.apiUrl = `${this.config.baseUrl}/${endpoint}`;
|
|
106
|
+
}
|
|
107
|
+
// ─── CRUD Operations ──────────────────────────────────────
|
|
108
|
+
/**
|
|
109
|
+
* Tüm kayıtları getirir
|
|
110
|
+
*/
|
|
111
|
+
getAll(params) {
|
|
112
|
+
this._loading.set(true);
|
|
113
|
+
this._error.set(null);
|
|
114
|
+
const httpParams = this.buildHttpParams(params);
|
|
115
|
+
return this.http.get(this.apiUrl, { params: httpParams }).pipe(tap((data) => {
|
|
116
|
+
this._items.set(data);
|
|
117
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Sayfalı kayıtları getirir
|
|
121
|
+
*/
|
|
122
|
+
getPaged(params) {
|
|
123
|
+
this._loading.set(true);
|
|
124
|
+
this._error.set(null);
|
|
125
|
+
const queryParams = {
|
|
126
|
+
page: this._currentPage(),
|
|
127
|
+
pageSize: this._pageSize(),
|
|
128
|
+
...params,
|
|
129
|
+
};
|
|
130
|
+
const httpParams = this.buildHttpParams(queryParams);
|
|
131
|
+
return this.http
|
|
132
|
+
.get(this.apiUrl, { params: httpParams })
|
|
133
|
+
.pipe(tap((response) => {
|
|
134
|
+
this._items.set(response.data);
|
|
135
|
+
this._totalCount.set(response.totalCount);
|
|
136
|
+
this._currentPage.set(response.page);
|
|
137
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* ID ile tek kayıt getirir
|
|
141
|
+
*/
|
|
142
|
+
getById(id) {
|
|
143
|
+
this._loading.set(true);
|
|
144
|
+
this._error.set(null);
|
|
145
|
+
return this.http.get(`${this.apiUrl}/${id}`).pipe(tap((item) => {
|
|
146
|
+
this._selectedItem.set(item);
|
|
147
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Yeni kayıt oluşturur
|
|
151
|
+
*/
|
|
152
|
+
create(item) {
|
|
153
|
+
this._loading.set(true);
|
|
154
|
+
this._error.set(null);
|
|
155
|
+
return this.http.post(this.apiUrl, item).pipe(tap((created) => {
|
|
156
|
+
this._items.update((items) => [...items, created]);
|
|
157
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Mevcut kaydı günceller
|
|
161
|
+
*/
|
|
162
|
+
update(id, item) {
|
|
163
|
+
this._loading.set(true);
|
|
164
|
+
this._error.set(null);
|
|
165
|
+
return this.http.put(`${this.apiUrl}/${id}`, item).pipe(tap((updated) => {
|
|
166
|
+
this._items.update((items) => items.map((i) => (i.id === id ? updated : i)));
|
|
167
|
+
if (this._selectedItem()?.id === id) {
|
|
168
|
+
this._selectedItem.set(updated);
|
|
169
|
+
}
|
|
170
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Kaydı kısmen günceller (PATCH)
|
|
174
|
+
*/
|
|
175
|
+
patch(id, item) {
|
|
176
|
+
this._loading.set(true);
|
|
177
|
+
this._error.set(null);
|
|
178
|
+
return this.http.patch(`${this.apiUrl}/${id}`, item).pipe(tap((updated) => {
|
|
179
|
+
this._items.update((items) => items.map((i) => (i.id === id ? updated : i)));
|
|
180
|
+
if (this._selectedItem()?.id === id) {
|
|
181
|
+
this._selectedItem.set(updated);
|
|
182
|
+
}
|
|
183
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Kaydı siler
|
|
187
|
+
*/
|
|
188
|
+
delete(id) {
|
|
189
|
+
this._loading.set(true);
|
|
190
|
+
this._error.set(null);
|
|
191
|
+
return this.http.delete(`${this.apiUrl}/${id}`).pipe(tap(() => {
|
|
192
|
+
this._items.update((items) => items.filter((i) => i.id !== id));
|
|
193
|
+
if (this._selectedItem()?.id === id) {
|
|
194
|
+
this._selectedItem.set(null);
|
|
195
|
+
}
|
|
196
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
197
|
+
}
|
|
198
|
+
// ─── Bulk Operations ──────────────────────────────────────
|
|
199
|
+
/**
|
|
200
|
+
* Toplu silme
|
|
201
|
+
*/
|
|
202
|
+
deleteMany(ids) {
|
|
203
|
+
this._loading.set(true);
|
|
204
|
+
this._error.set(null);
|
|
205
|
+
return this.http
|
|
206
|
+
.request('DELETE', this.apiUrl, { body: { ids } })
|
|
207
|
+
.pipe(tap(() => {
|
|
208
|
+
this._items.update((items) => items.filter((i) => !ids.includes(i.id)));
|
|
209
|
+
if (this._selectedItem() && ids.includes(this._selectedItem().id)) {
|
|
210
|
+
this._selectedItem.set(null);
|
|
211
|
+
}
|
|
212
|
+
}), catchError((err) => this.handleError(err)), finalize(() => this._loading.set(false)));
|
|
213
|
+
}
|
|
214
|
+
// ─── Search & Filter ──────────────────────────────────────
|
|
215
|
+
/**
|
|
216
|
+
* Arama yapar
|
|
217
|
+
*/
|
|
218
|
+
search(term, params) {
|
|
219
|
+
return this.getAll({ ...params, search: term });
|
|
220
|
+
}
|
|
221
|
+
// ─── Pagination Controls ──────────────────────────────────
|
|
222
|
+
/**
|
|
223
|
+
* Belirtilen sayfaya gider
|
|
224
|
+
*/
|
|
225
|
+
goToPage(page) {
|
|
226
|
+
this._currentPage.set(page);
|
|
227
|
+
return this.getPaged();
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Sonraki sayfa
|
|
231
|
+
*/
|
|
232
|
+
nextPage() {
|
|
233
|
+
if (this._currentPage() < this.totalPages()) {
|
|
234
|
+
this._currentPage.update((p) => p + 1);
|
|
235
|
+
}
|
|
236
|
+
return this.getPaged();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Önceki sayfa
|
|
240
|
+
*/
|
|
241
|
+
previousPage() {
|
|
242
|
+
if (this._currentPage() > 1) {
|
|
243
|
+
this._currentPage.update((p) => p - 1);
|
|
244
|
+
}
|
|
245
|
+
return this.getPaged();
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Sayfa boyutunu değiştirir
|
|
249
|
+
*/
|
|
250
|
+
setPageSize(size) {
|
|
251
|
+
this._pageSize.set(size);
|
|
252
|
+
this._currentPage.set(1);
|
|
253
|
+
}
|
|
254
|
+
// ─── State Management ─────────────────────────────────────
|
|
255
|
+
/**
|
|
256
|
+
* Seçili entity'yi ayarlar
|
|
257
|
+
*/
|
|
258
|
+
select(item) {
|
|
259
|
+
this._selectedItem.set(item);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* State'i sıfırlar
|
|
263
|
+
*/
|
|
264
|
+
clearState() {
|
|
265
|
+
this._items.set([]);
|
|
266
|
+
this._selectedItem.set(null);
|
|
267
|
+
this._error.set(null);
|
|
268
|
+
this._totalCount.set(0);
|
|
269
|
+
this._currentPage.set(1);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Hatayı temizler
|
|
273
|
+
*/
|
|
274
|
+
clearError() {
|
|
275
|
+
this._error.set(null);
|
|
276
|
+
}
|
|
277
|
+
// ─── Protected Helpers ────────────────────────────────────
|
|
278
|
+
/**
|
|
279
|
+
* QueryParams'ı HttpParams'a dönüştürür.
|
|
280
|
+
* Alt sınıflarda override edilebilir.
|
|
281
|
+
*/
|
|
282
|
+
buildHttpParams(params) {
|
|
283
|
+
let httpParams = new HttpParams();
|
|
284
|
+
if (!params)
|
|
285
|
+
return httpParams;
|
|
286
|
+
if (params.page != null) {
|
|
287
|
+
httpParams = httpParams.set('page', params.page.toString());
|
|
288
|
+
}
|
|
289
|
+
if (params.pageSize != null) {
|
|
290
|
+
httpParams = httpParams.set('pageSize', params.pageSize.toString());
|
|
291
|
+
}
|
|
292
|
+
if (params.sort) {
|
|
293
|
+
httpParams = httpParams.set('sort', params.sort);
|
|
294
|
+
}
|
|
295
|
+
if (params.sortDirection) {
|
|
296
|
+
httpParams = httpParams.set('sortDirection', params.sortDirection);
|
|
297
|
+
}
|
|
298
|
+
if (params.search) {
|
|
299
|
+
httpParams = httpParams.set('search', params.search);
|
|
300
|
+
}
|
|
301
|
+
if (params.filters) {
|
|
302
|
+
for (const [key, value] of Object.entries(params.filters)) {
|
|
303
|
+
httpParams = httpParams.set(key, String(value));
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return httpParams;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* HTTP hatalarını yönetir.
|
|
310
|
+
* Alt sınıflarda override edilebilir.
|
|
311
|
+
*/
|
|
312
|
+
handleError(error) {
|
|
313
|
+
const apiError = {
|
|
314
|
+
status: error.status,
|
|
315
|
+
message: error.error?.message ?? error.message ?? 'Bilinmeyen hata',
|
|
316
|
+
errors: error.error?.errors,
|
|
317
|
+
timestamp: new Date().toISOString(),
|
|
318
|
+
};
|
|
319
|
+
this._error.set(apiError);
|
|
320
|
+
if (this.config.enableLogging) {
|
|
321
|
+
console.error(`[GenericService] ${this.endpoint}:`, apiError);
|
|
322
|
+
}
|
|
323
|
+
return throwError(() => apiError);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/*
|
|
328
|
+
* Public API Surface
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Generated bundle index. Do not edit.
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
export { GENERIC_SERVICE_CONFIG, GenericService, provideGenericServiceConfig };
|
|
336
|
+
//# sourceMappingURL=acy-generic-service.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acy-generic-service.mjs","sources":["../../src/lib/generic-service.config.ts","../../src/lib/generic.service.ts","../../src/public-api.ts","../../src/acy-generic-service.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\r\n\r\n/**\r\n * Generic service konfigürasyonu\r\n */\r\nexport interface GenericServiceConfig {\r\n /** API base URL (örn: 'https://api.example.com') */\r\n baseUrl: string;\r\n /** Varsayılan sayfa boyutu */\r\n defaultPageSize?: number;\r\n /** İsteklere eklenecek varsayılan header'lar */\r\n defaultHeaders?: Record<string, string>;\r\n /** Hata mesajlarını loglama */\r\n enableLogging?: boolean;\r\n}\r\n\r\n/**\r\n * Konfigürasyon için InjectionToken\r\n */\r\nexport const GENERIC_SERVICE_CONFIG = new InjectionToken<GenericServiceConfig>(\r\n 'GENERIC_SERVICE_CONFIG'\r\n);\r\n\r\n/**\r\n * Konfigürasyonu provide etmek için yardımcı fonksiyon\r\n *\r\n * @example\r\n * ```ts\r\n * // app.config.ts\r\n * export const appConfig: ApplicationConfig = {\r\n * providers: [\r\n * provideGenericServiceConfig({\r\n * baseUrl: 'https://api.example.com',\r\n * defaultPageSize: 20,\r\n * enableLogging: true,\r\n * }),\r\n * ],\r\n * };\r\n * ```\r\n */\r\nexport function provideGenericServiceConfig(config: GenericServiceConfig) {\r\n return {\r\n provide: GENERIC_SERVICE_CONFIG,\r\n useValue: {\r\n defaultPageSize: 10,\r\n enableLogging: false,\r\n ...config,\r\n } satisfies GenericServiceConfig,\r\n };\r\n}\r\n","import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';\r\nimport { inject, signal, computed } from '@angular/core';\r\nimport { Observable, throwError, catchError, tap, map, finalize } from 'rxjs';\r\n\r\nimport { GENERIC_SERVICE_CONFIG, GenericServiceConfig } from './generic-service.config';\r\nimport {\r\n BaseEntity,\r\n PagedResponse,\r\n QueryParams,\r\n ApiResponse,\r\n ApiError,\r\n} from './models';\r\n\r\n/**\r\n * Angular 21 Generic CRUD Service\r\n *\r\n * Tüm entity servisleri için temel sınıf.\r\n * HTTP CRUD operasyonları, pagination, filtreleme, hata yönetimi ve\r\n * reactive state (signals) desteği sağlar.\r\n *\r\n * @example\r\n * ```ts\r\n * // user.service.ts\r\n * @Injectable({ providedIn: 'root' })\r\n * export class UserService extends GenericService<User> {\r\n * constructor() {\r\n * super('users'); // endpoint: /api/users\r\n * }\r\n * }\r\n *\r\n * // component içinde kullanım\r\n * export class UserListComponent {\r\n * private userService = inject(UserService);\r\n *\r\n * users = this.userService.items;\r\n * loading = this.userService.loading;\r\n * error = this.userService.error;\r\n * totalCount = this.userService.totalCount;\r\n * }\r\n * ```\r\n */\r\nexport abstract class GenericService<T extends BaseEntity> {\r\n // Angular 21 inject() ile DI\r\n protected readonly http = inject(HttpClient);\r\n protected readonly config = inject<GenericServiceConfig>(GENERIC_SERVICE_CONFIG);\r\n\r\n // Reactive state — Angular Signals\r\n private readonly _items = signal<T[]>([]);\r\n private readonly _selectedItem = signal<T | null>(null);\r\n private readonly _loading = signal(false);\r\n private readonly _error = signal<ApiError | null>(null);\r\n private readonly _totalCount = signal(0);\r\n private readonly _currentPage = signal(1);\r\n private readonly _pageSize: ReturnType<typeof signal<number>>;\r\n\r\n /** Mevcut entity listesi (readonly signal) */\r\n readonly items = this._items.asReadonly();\r\n\r\n /** Seçili entity (readonly signal) */\r\n readonly selectedItem = this._selectedItem.asReadonly();\r\n\r\n /** Yüklenme durumu */\r\n readonly loading = this._loading.asReadonly();\r\n\r\n /** Son hata */\r\n readonly error = this._error.asReadonly();\r\n\r\n /** Toplam kayıt sayısı */\r\n readonly totalCount = this._totalCount.asReadonly();\r\n\r\n /** Mevcut sayfa */\r\n readonly currentPage = this._currentPage.asReadonly();\r\n\r\n /** Sayfa boyutu */\r\n readonly pageSize: ReturnType<typeof computed<number>>;\r\n\r\n /** Toplam sayfa sayısı (computed) */\r\n readonly totalPages = computed(() =>\r\n Math.ceil(this._totalCount() / this._pageSize())\r\n );\r\n\r\n /** Veri var mı? (computed) */\r\n readonly hasData = computed(() => this._items().length > 0);\r\n\r\n /** Tam API URL */\r\n protected readonly apiUrl: string;\r\n\r\n /**\r\n * @param endpoint - API endpoint yolu (örn: 'users', 'products')\r\n */\r\n constructor(protected readonly endpoint: string) {\r\n this._pageSize = signal(this.config.defaultPageSize ?? 10);\r\n this.pageSize = computed(() => this._pageSize());\r\n this.apiUrl = `${this.config.baseUrl}/${endpoint}`;\r\n }\r\n\r\n // ─── CRUD Operations ──────────────────────────────────────\r\n\r\n /**\r\n * Tüm kayıtları getirir\r\n */\r\n getAll(params?: QueryParams): Observable<T[]> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n const httpParams = this.buildHttpParams(params);\r\n\r\n return this.http.get<T[]>(this.apiUrl, { params: httpParams }).pipe(\r\n tap((data) => {\r\n this._items.set(data);\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * Sayfalı kayıtları getirir\r\n */\r\n getPaged(params?: QueryParams): Observable<PagedResponse<T>> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n const queryParams: QueryParams = {\r\n page: this._currentPage(),\r\n pageSize: this._pageSize(),\r\n ...params,\r\n };\r\n\r\n const httpParams = this.buildHttpParams(queryParams);\r\n\r\n return this.http\r\n .get<PagedResponse<T>>(this.apiUrl, { params: httpParams })\r\n .pipe(\r\n tap((response) => {\r\n this._items.set(response.data);\r\n this._totalCount.set(response.totalCount);\r\n this._currentPage.set(response.page);\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * ID ile tek kayıt getirir\r\n */\r\n getById(id: string | number): Observable<T> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http.get<T>(`${this.apiUrl}/${id}`).pipe(\r\n tap((item) => {\r\n this._selectedItem.set(item);\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * Yeni kayıt oluşturur\r\n */\r\n create(item: Omit<T, 'id'>): Observable<T> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http.post<T>(this.apiUrl, item).pipe(\r\n tap((created) => {\r\n this._items.update((items) => [...items, created]);\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * Mevcut kaydı günceller\r\n */\r\n update(id: string | number, item: Partial<T>): Observable<T> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http.put<T>(`${this.apiUrl}/${id}`, item).pipe(\r\n tap((updated) => {\r\n this._items.update((items) =>\r\n items.map((i) => (i.id === id ? updated : i))\r\n );\r\n if (this._selectedItem()?.id === id) {\r\n this._selectedItem.set(updated);\r\n }\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * Kaydı kısmen günceller (PATCH)\r\n */\r\n patch(id: string | number, item: Partial<T>): Observable<T> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http.patch<T>(`${this.apiUrl}/${id}`, item).pipe(\r\n tap((updated) => {\r\n this._items.update((items) =>\r\n items.map((i) => (i.id === id ? updated : i))\r\n );\r\n if (this._selectedItem()?.id === id) {\r\n this._selectedItem.set(updated);\r\n }\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n /**\r\n * Kaydı siler\r\n */\r\n delete(id: string | number): Observable<void> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(\r\n tap(() => {\r\n this._items.update((items) => items.filter((i) => i.id !== id));\r\n if (this._selectedItem()?.id === id) {\r\n this._selectedItem.set(null);\r\n }\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n // ─── Bulk Operations ──────────────────────────────────────\r\n\r\n /**\r\n * Toplu silme\r\n */\r\n deleteMany(ids: (string | number)[]): Observable<void> {\r\n this._loading.set(true);\r\n this._error.set(null);\r\n\r\n return this.http\r\n .request<void>('DELETE', this.apiUrl, { body: { ids } })\r\n .pipe(\r\n tap(() => {\r\n this._items.update((items) =>\r\n items.filter((i) => !ids.includes(i.id))\r\n );\r\n if (this._selectedItem() && ids.includes(this._selectedItem()!.id)) {\r\n this._selectedItem.set(null);\r\n }\r\n }),\r\n catchError((err) => this.handleError(err)),\r\n finalize(() => this._loading.set(false))\r\n );\r\n }\r\n\r\n // ─── Search & Filter ──────────────────────────────────────\r\n\r\n /**\r\n * Arama yapar\r\n */\r\n search(term: string, params?: QueryParams): Observable<T[]> {\r\n return this.getAll({ ...params, search: term });\r\n }\r\n\r\n // ─── Pagination Controls ──────────────────────────────────\r\n\r\n /**\r\n * Belirtilen sayfaya gider\r\n */\r\n goToPage(page: number): Observable<PagedResponse<T>> {\r\n this._currentPage.set(page);\r\n return this.getPaged();\r\n }\r\n\r\n /**\r\n * Sonraki sayfa\r\n */\r\n nextPage(): Observable<PagedResponse<T>> {\r\n if (this._currentPage() < this.totalPages()) {\r\n this._currentPage.update((p) => p + 1);\r\n }\r\n return this.getPaged();\r\n }\r\n\r\n /**\r\n * Önceki sayfa\r\n */\r\n previousPage(): Observable<PagedResponse<T>> {\r\n if (this._currentPage() > 1) {\r\n this._currentPage.update((p) => p - 1);\r\n }\r\n return this.getPaged();\r\n }\r\n\r\n /**\r\n * Sayfa boyutunu değiştirir\r\n */\r\n setPageSize(size: number): void {\r\n this._pageSize.set(size);\r\n this._currentPage.set(1);\r\n }\r\n\r\n // ─── State Management ─────────────────────────────────────\r\n\r\n /**\r\n * Seçili entity'yi ayarlar\r\n */\r\n select(item: T | null): void {\r\n this._selectedItem.set(item);\r\n }\r\n\r\n /**\r\n * State'i sıfırlar\r\n */\r\n clearState(): void {\r\n this._items.set([]);\r\n this._selectedItem.set(null);\r\n this._error.set(null);\r\n this._totalCount.set(0);\r\n this._currentPage.set(1);\r\n }\r\n\r\n /**\r\n * Hatayı temizler\r\n */\r\n clearError(): void {\r\n this._error.set(null);\r\n }\r\n\r\n // ─── Protected Helpers ────────────────────────────────────\r\n\r\n /**\r\n * QueryParams'ı HttpParams'a dönüştürür.\r\n * Alt sınıflarda override edilebilir.\r\n */\r\n protected buildHttpParams(params?: QueryParams): HttpParams {\r\n let httpParams = new HttpParams();\r\n\r\n if (!params) return httpParams;\r\n\r\n if (params.page != null) {\r\n httpParams = httpParams.set('page', params.page.toString());\r\n }\r\n if (params.pageSize != null) {\r\n httpParams = httpParams.set('pageSize', params.pageSize.toString());\r\n }\r\n if (params.sort) {\r\n httpParams = httpParams.set('sort', params.sort);\r\n }\r\n if (params.sortDirection) {\r\n httpParams = httpParams.set('sortDirection', params.sortDirection);\r\n }\r\n if (params.search) {\r\n httpParams = httpParams.set('search', params.search);\r\n }\r\n if (params.filters) {\r\n for (const [key, value] of Object.entries(params.filters)) {\r\n httpParams = httpParams.set(key, String(value));\r\n }\r\n }\r\n\r\n return httpParams;\r\n }\r\n\r\n /**\r\n * HTTP hatalarını yönetir.\r\n * Alt sınıflarda override edilebilir.\r\n */\r\n protected handleError(error: HttpErrorResponse): Observable<never> {\r\n const apiError: ApiError = {\r\n status: error.status,\r\n message: error.error?.message ?? error.message ?? 'Bilinmeyen hata',\r\n errors: error.error?.errors,\r\n timestamp: new Date().toISOString(),\r\n };\r\n\r\n this._error.set(apiError);\r\n\r\n if (this.config.enableLogging) {\r\n console.error(`[GenericService] ${this.endpoint}:`, apiError);\r\n }\r\n\r\n return throwError(() => apiError);\r\n }\r\n}\r\n","/*\r\n * Public API Surface\r\n */\r\nexport { GenericService } from './lib/generic.service';\r\nexport {\r\n BaseEntity,\r\n PagedResponse,\r\n QueryParams,\r\n ApiResponse,\r\n ApiError,\r\n} from './lib/models';\r\nexport {\r\n GenericServiceConfig,\r\n GENERIC_SERVICE_CONFIG,\r\n provideGenericServiceConfig,\r\n} from './lib/generic-service.config';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { InjectionToken, computed } from '@angular/core';
|
|
3
|
+
import { HttpClient, HttpParams, HttpErrorResponse } from '@angular/common/http';
|
|
4
|
+
import { Observable } from 'rxjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generic service konfigürasyonu
|
|
8
|
+
*/
|
|
9
|
+
interface GenericServiceConfig {
|
|
10
|
+
/** API base URL (örn: 'https://api.example.com') */
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
/** Varsayılan sayfa boyutu */
|
|
13
|
+
defaultPageSize?: number;
|
|
14
|
+
/** İsteklere eklenecek varsayılan header'lar */
|
|
15
|
+
defaultHeaders?: Record<string, string>;
|
|
16
|
+
/** Hata mesajlarını loglama */
|
|
17
|
+
enableLogging?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Konfigürasyon için InjectionToken
|
|
21
|
+
*/
|
|
22
|
+
declare const GENERIC_SERVICE_CONFIG: InjectionToken<GenericServiceConfig>;
|
|
23
|
+
/**
|
|
24
|
+
* Konfigürasyonu provide etmek için yardımcı fonksiyon
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* // app.config.ts
|
|
29
|
+
* export const appConfig: ApplicationConfig = {
|
|
30
|
+
* providers: [
|
|
31
|
+
* provideGenericServiceConfig({
|
|
32
|
+
* baseUrl: 'https://api.example.com',
|
|
33
|
+
* defaultPageSize: 20,
|
|
34
|
+
* enableLogging: true,
|
|
35
|
+
* }),
|
|
36
|
+
* ],
|
|
37
|
+
* };
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function provideGenericServiceConfig(config: GenericServiceConfig): {
|
|
41
|
+
provide: InjectionToken<GenericServiceConfig>;
|
|
42
|
+
useValue: {
|
|
43
|
+
/** API base URL (örn: 'https://api.example.com') */
|
|
44
|
+
baseUrl: string;
|
|
45
|
+
defaultPageSize: number;
|
|
46
|
+
/** İsteklere eklenecek varsayılan header'lar */
|
|
47
|
+
defaultHeaders?: Record<string, string>;
|
|
48
|
+
enableLogging: boolean;
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Base entity interface - tüm entity'lerin bir ID'si olmalı
|
|
54
|
+
*/
|
|
55
|
+
interface BaseEntity {
|
|
56
|
+
id: string | number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Sayfalı yanıt modeli
|
|
60
|
+
*/
|
|
61
|
+
interface PagedResponse<T> {
|
|
62
|
+
data: T[];
|
|
63
|
+
totalCount: number;
|
|
64
|
+
page: number;
|
|
65
|
+
pageSize: number;
|
|
66
|
+
totalPages: number;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sorgu parametreleri
|
|
70
|
+
*/
|
|
71
|
+
interface QueryParams {
|
|
72
|
+
page?: number;
|
|
73
|
+
pageSize?: number;
|
|
74
|
+
sort?: string;
|
|
75
|
+
sortDirection?: 'asc' | 'desc';
|
|
76
|
+
search?: string;
|
|
77
|
+
filters?: Record<string, string | number | boolean>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* API yanıt sarmalayıcı
|
|
81
|
+
*/
|
|
82
|
+
interface ApiResponse<T> {
|
|
83
|
+
data: T;
|
|
84
|
+
success: boolean;
|
|
85
|
+
message?: string;
|
|
86
|
+
errors?: string[];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Hata detay modeli
|
|
90
|
+
*/
|
|
91
|
+
interface ApiError {
|
|
92
|
+
status: number;
|
|
93
|
+
message: string;
|
|
94
|
+
errors?: string[];
|
|
95
|
+
timestamp: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Angular 21 Generic CRUD Service
|
|
100
|
+
*
|
|
101
|
+
* Tüm entity servisleri için temel sınıf.
|
|
102
|
+
* HTTP CRUD operasyonları, pagination, filtreleme, hata yönetimi ve
|
|
103
|
+
* reactive state (signals) desteği sağlar.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* // user.service.ts
|
|
108
|
+
* @Injectable({ providedIn: 'root' })
|
|
109
|
+
* export class UserService extends GenericService<User> {
|
|
110
|
+
* constructor() {
|
|
111
|
+
* super('users'); // endpoint: /api/users
|
|
112
|
+
* }
|
|
113
|
+
* }
|
|
114
|
+
*
|
|
115
|
+
* // component içinde kullanım
|
|
116
|
+
* export class UserListComponent {
|
|
117
|
+
* private userService = inject(UserService);
|
|
118
|
+
*
|
|
119
|
+
* users = this.userService.items;
|
|
120
|
+
* loading = this.userService.loading;
|
|
121
|
+
* error = this.userService.error;
|
|
122
|
+
* totalCount = this.userService.totalCount;
|
|
123
|
+
* }
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
declare abstract class GenericService<T extends BaseEntity> {
|
|
127
|
+
protected readonly endpoint: string;
|
|
128
|
+
protected readonly http: HttpClient;
|
|
129
|
+
protected readonly config: GenericServiceConfig;
|
|
130
|
+
private readonly _items;
|
|
131
|
+
private readonly _selectedItem;
|
|
132
|
+
private readonly _loading;
|
|
133
|
+
private readonly _error;
|
|
134
|
+
private readonly _totalCount;
|
|
135
|
+
private readonly _currentPage;
|
|
136
|
+
private readonly _pageSize;
|
|
137
|
+
/** Mevcut entity listesi (readonly signal) */
|
|
138
|
+
readonly items: _angular_core.Signal<T[]>;
|
|
139
|
+
/** Seçili entity (readonly signal) */
|
|
140
|
+
readonly selectedItem: _angular_core.Signal<T>;
|
|
141
|
+
/** Yüklenme durumu */
|
|
142
|
+
readonly loading: _angular_core.Signal<boolean>;
|
|
143
|
+
/** Son hata */
|
|
144
|
+
readonly error: _angular_core.Signal<ApiError>;
|
|
145
|
+
/** Toplam kayıt sayısı */
|
|
146
|
+
readonly totalCount: _angular_core.Signal<number>;
|
|
147
|
+
/** Mevcut sayfa */
|
|
148
|
+
readonly currentPage: _angular_core.Signal<number>;
|
|
149
|
+
/** Sayfa boyutu */
|
|
150
|
+
readonly pageSize: ReturnType<typeof computed<number>>;
|
|
151
|
+
/** Toplam sayfa sayısı (computed) */
|
|
152
|
+
readonly totalPages: _angular_core.Signal<number>;
|
|
153
|
+
/** Veri var mı? (computed) */
|
|
154
|
+
readonly hasData: _angular_core.Signal<boolean>;
|
|
155
|
+
/** Tam API URL */
|
|
156
|
+
protected readonly apiUrl: string;
|
|
157
|
+
/**
|
|
158
|
+
* @param endpoint - API endpoint yolu (örn: 'users', 'products')
|
|
159
|
+
*/
|
|
160
|
+
constructor(endpoint: string);
|
|
161
|
+
/**
|
|
162
|
+
* Tüm kayıtları getirir
|
|
163
|
+
*/
|
|
164
|
+
getAll(params?: QueryParams): Observable<T[]>;
|
|
165
|
+
/**
|
|
166
|
+
* Sayfalı kayıtları getirir
|
|
167
|
+
*/
|
|
168
|
+
getPaged(params?: QueryParams): Observable<PagedResponse<T>>;
|
|
169
|
+
/**
|
|
170
|
+
* ID ile tek kayıt getirir
|
|
171
|
+
*/
|
|
172
|
+
getById(id: string | number): Observable<T>;
|
|
173
|
+
/**
|
|
174
|
+
* Yeni kayıt oluşturur
|
|
175
|
+
*/
|
|
176
|
+
create(item: Omit<T, 'id'>): Observable<T>;
|
|
177
|
+
/**
|
|
178
|
+
* Mevcut kaydı günceller
|
|
179
|
+
*/
|
|
180
|
+
update(id: string | number, item: Partial<T>): Observable<T>;
|
|
181
|
+
/**
|
|
182
|
+
* Kaydı kısmen günceller (PATCH)
|
|
183
|
+
*/
|
|
184
|
+
patch(id: string | number, item: Partial<T>): Observable<T>;
|
|
185
|
+
/**
|
|
186
|
+
* Kaydı siler
|
|
187
|
+
*/
|
|
188
|
+
delete(id: string | number): Observable<void>;
|
|
189
|
+
/**
|
|
190
|
+
* Toplu silme
|
|
191
|
+
*/
|
|
192
|
+
deleteMany(ids: (string | number)[]): Observable<void>;
|
|
193
|
+
/**
|
|
194
|
+
* Arama yapar
|
|
195
|
+
*/
|
|
196
|
+
search(term: string, params?: QueryParams): Observable<T[]>;
|
|
197
|
+
/**
|
|
198
|
+
* Belirtilen sayfaya gider
|
|
199
|
+
*/
|
|
200
|
+
goToPage(page: number): Observable<PagedResponse<T>>;
|
|
201
|
+
/**
|
|
202
|
+
* Sonraki sayfa
|
|
203
|
+
*/
|
|
204
|
+
nextPage(): Observable<PagedResponse<T>>;
|
|
205
|
+
/**
|
|
206
|
+
* Önceki sayfa
|
|
207
|
+
*/
|
|
208
|
+
previousPage(): Observable<PagedResponse<T>>;
|
|
209
|
+
/**
|
|
210
|
+
* Sayfa boyutunu değiştirir
|
|
211
|
+
*/
|
|
212
|
+
setPageSize(size: number): void;
|
|
213
|
+
/**
|
|
214
|
+
* Seçili entity'yi ayarlar
|
|
215
|
+
*/
|
|
216
|
+
select(item: T | null): void;
|
|
217
|
+
/**
|
|
218
|
+
* State'i sıfırlar
|
|
219
|
+
*/
|
|
220
|
+
clearState(): void;
|
|
221
|
+
/**
|
|
222
|
+
* Hatayı temizler
|
|
223
|
+
*/
|
|
224
|
+
clearError(): void;
|
|
225
|
+
/**
|
|
226
|
+
* QueryParams'ı HttpParams'a dönüştürür.
|
|
227
|
+
* Alt sınıflarda override edilebilir.
|
|
228
|
+
*/
|
|
229
|
+
protected buildHttpParams(params?: QueryParams): HttpParams;
|
|
230
|
+
/**
|
|
231
|
+
* HTTP hatalarını yönetir.
|
|
232
|
+
* Alt sınıflarda override edilebilir.
|
|
233
|
+
*/
|
|
234
|
+
protected handleError(error: HttpErrorResponse): Observable<never>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export { GENERIC_SERVICE_CONFIG, GenericService, provideGenericServiceConfig };
|
|
238
|
+
export type { ApiError, ApiResponse, BaseEntity, GenericServiceConfig, PagedResponse, QueryParams };
|
package/ng-package.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alicanyucelankara06/generic-service",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Angular 21 Generic CRUD Service Package",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@angular/common": "^21.0.0",
|
|
8
|
+
"@angular/core": "^21.0.0",
|
|
9
|
+
"rxjs": "^7.8.0"
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@angular/common": "^21.0.0",
|
|
13
|
+
"@angular/compiler": "^21.0.0",
|
|
14
|
+
"@angular/compiler-cli": "^21.0.0",
|
|
15
|
+
"@angular/core": "^21.0.0",
|
|
16
|
+
"@angular/platform-browser": "^21.0.0",
|
|
17
|
+
"ng-packagr": "^21.0.0",
|
|
18
|
+
"rxjs": "^7.8.0",
|
|
19
|
+
"typescript": "~5.9.0",
|
|
20
|
+
"zone.js": "~0.15.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "ng-packagr -p ng-package.json",
|
|
24
|
+
"build:watch": "ng-packagr -p ng-package.json --watch"
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false
|
|
27
|
+
}
|