Update src/app/services/crudServices/crud.service.ts

This commit is contained in:
ChrisChen 2025-11-03 17:10:18 +00:00
parent 9fec45a91f
commit fd32ae5dcc

View File

@ -178,3 +178,165 @@ export class CombinedKeyCrudService<T> implements ICrudService<T> {
}
}
// Type definitions
type TextResponse = { message: string };
/**
* Base CRUD service that targets the provided controller path.
*
* It mirrors the endpoints of CrudBaseApiController<T>:
* GET /api/{controller}
* GET /api/{controller}/{id}
* POST /api/{controller} -> string
* POST /api/{controller}/batch -> string[]
* PUT /api/{controller}
* PUT /api/{controller}/batch -> number
* DELETE /api/{controller}/{id}
* DELETE /api/{controller}/batch -> text summary
* GET /api/{controller}/{id}/exists -> boolean
* GET /api/{controller}/count -> number
*/
@Injectable({ providedIn: 'root' })
export class CrudBaseApiService<T extends object> {
/**
* Example: baseUrl = 'https://your-api', controller = 'Customer'
* endpoint = 'https://your-api/api/Customer'
*/
protected readonly endpoint: string;
/**
* @param http Angular HttpClient
* @param baseUrl API root without trailing slash (e.g., environment.apiBaseUrl)
* @param controllerName Controller name (e.g., 'Customer', 'Orders')
*/
constructor(
protected http: HttpClient,
protected apiConfig: ApiConfigService,
@Inject(String) private controllerName: string
) {
this.endpoint = apiConfig.getApiUrl(this.controllerName);
}
/** Optional default headers (JSON). Override in subclasses if needed. */
protected get jsonHeaders(): HttpHeaders {
return new HttpHeaders({ 'Content-Type': 'application/json' });
}
/** Shared error handler that surfaces useful messages. */
protected handleError(error: HttpErrorResponse): Observable<never> {
let msg = 'Unknown error';
if (error.error instanceof Blob) {
// In case backend returns text/plain; charset=utf-8 as Blob
return throwError(() => new Error('Server returned an error blob'));
}
if (typeof error.error === 'string') msg = error.error;
else if (error.error?.message) msg = error.error.message;
else if (error.message) msg = error.message;
return throwError(() => new Error(msg));
}
/** Prepare the response for the given entity. Override in subclasses if needed. */
protected prepareResponse(response: T): T {
// Do nothing by default
return response;
}
/** GET /api/{controller} */
getAll(): Observable<T[]> {
return this.http
.get<T[]>(this.endpoint)
.pipe(
map(response => {
for (let i = 0; i < response.length; i++) {
const element = response[i];
response[i] = this.prepareResponse(element);
}
return response;
}),
catchError(err => this.handleError(err)));
}
/** GET /api/{controller}/{id} */
getById(id: string): Observable<T> {
return this.http
.get<T>(`${this.endpoint}/${id}`)
.pipe(
map(response => this.prepareResponse(response)),
catchError(err => this.handleError(err)));
}
/** POST /api/{controller} -> string */
create(entity: T): Observable<string> {
return this.http
.post<string>(this.endpoint, entity, { headers: this.jsonHeaders })
.pipe(catchError(err => this.handleError(err)));
}
/** POST /api/{controller}/batch -> string[] */
createRange(entities: T[]): Observable<string[]> {
return this.http
.post<string[]>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
.pipe(catchError(err => this.handleError(err)));
}
/** PUT /api/{controller} */
update(entity: T): Observable<void> {
return this.http
.put<void>(this.endpoint, entity, { headers: this.jsonHeaders })
.pipe(catchError(err => this.handleError(err)));
}
/** PUT /api/{controller}/batch -> number (updated count) */
updateRange(entities: T[]): Observable<number> {
return this.http
.put<number>(`${this.endpoint}/batch`, entities, { headers: this.jsonHeaders })
.pipe(catchError(err => this.handleError(err)));
}
/** DELETE /api/{controller}/{id} */
delete(id: string): Observable<void> {
return this.http
.delete<void>(`${this.endpoint}/${id}`)
.pipe(catchError(err => this.handleError(err)));
}
/** DELETE /api/{controller}/batch -> text summary */
deleteRange(ids: string[]): Observable<TextResponse> {
// API returns a plain text message; map it into a TextResponse for convenience
return this.http
.delete(`${this.endpoint}/batch`, {
body: ids,
headers: this.jsonHeaders
})
.pipe(
map((response: any) => ({ message: response || 'Batch delete completed' })),
catchError(err => this.handleError(err))
);
}
/** GET /api/{controller}/{id}/exists -> boolean */
exists(id: string): Observable<boolean> {
return this.http
.get<boolean>(`${this.endpoint}/${id}/exists`)
.pipe(catchError(err => this.handleError(err)));
}
/** GET /api/{controller}/count -> number */
count(): Observable<number> {
return this.http
.get<number>(`${this.endpoint}/count`)
.pipe(catchError(err => this.handleError(err)));
}
}