npm package: @libs-ui/services-base-request-abstract
Description: Abstract base class cung cấp toàn bộ infrastructure cho HTTP requests trong Angular: GET, POST, PUT, PATCH, DELETE, file upload với progress tracking, response normalization, error handling tự động, caching với IndexedDB và page zero conversion.
Latest version: 0.2.356-23

# Base Request Abstract Service

Abstract base class cung cấp toàn bộ infrastructure cho HTTP requests trong Angular: GET, POST, PUT, PATCH, DELETE, file upload với progress tracking, response normalization, error handling tự động, caching với IndexedDB và page zero conversion.

## Tính năng

- ✅ HTTP methods đầy đủ: GET, POST, PUT, PATCH, DELETE
- ✅ URL-encoded form: `postUrlEndCode`, `patchUrlEndCode` (content-type `application/x-www-form-urlencoded`)
- ✅ File upload với progress tracking qua XHR native (`sendWithFile`)
- ✅ Response normalization tự động — hỗ trợ nhiều backend format
- ✅ Pagination tự động từ nhiều format (paging, Spring pagination, cursor-based)
- ✅ Error handling: 401 → auto redirect, phân biệt 4xx vs 5xx
- ✅ Caching với IndexedDB (`cacheIndexDB`) — phân biệt theo user
- ✅ Page zero conversion (`isStartZeroPage`) — UI 1-based ↔ API 0-based

## Cài đặt

```bash
npm install @libs-ui/services-base-request-abstract
```

## Import

```typescript
import { LibsUiBaseRequestAbstractService } from '@libs-ui/services-base-request-abstract';
```

## Cách sử dụng

### Bước 1: Extend và implement 3 abstract methods bắt buộc

```typescript
import { Injectable } from '@angular/core';
import { HttpHeaders, HttpParams } from '@angular/common/http';
import { Router } from '@angular/router';
import { LibsUiBaseRequestAbstractService } from '@libs-ui/services-base-request-abstract';
import { UtilsHttpParamsRequest } from '@libs-ui/utils';

@Injectable({ providedIn: 'root' })
export class ApiService extends LibsUiBaseRequestAbstractService {
  protected override baseUrl = 'https://api.example.com';

  constructor(private router: Router) {
    super();
    this.keyCacheUniqueByIdUserLogin = localStorage.getItem('userId') || '';
  }

  protected getOptions<T>(params: UtilsHttpParamsRequest<T>, contentType?: string) {
    const token = localStorage.getItem('accessToken');
    return {
      headers: new HttpHeaders({
        'Content-Type': contentType || 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      }),
      params,
    };
  }

  protected redirectToLogin(): void {
    this.router.navigate(['/login']);
  }

  protected replaceURLByPattern<T>(url: string, params: UtilsHttpParamsRequest<T>) {
    let resultUrl = url;
    const pattern = /:([\w]+)/g;
    let match;
    while ((match = pattern.exec(url)) !== null) {
      const key = match[1];
      if (params.has(key)) {
        resultUrl = resultUrl.replace(`:${key}`, params.get(key)!);
        params = params.delete(key);
      }
    }
    return { url: resultUrl, params };
  }
}
```

### Bước 2: Tạo feature service kế thừa ApiService

```typescript
@Injectable({ providedIn: 'root' })
export class UserService extends ApiService {
  getUsers(params?: { page?: number; size?: number }) {
    return this.get<User[]>('/users', new UtilsHttpParamsRequest(undefined, params));
  }

  getUserById(id: string) {
    return this.get<User>('/users/:id', new UtilsHttpParamsRequest(undefined, { id }));
  }

  createUser(body: CreateUserDto) {
    return this.post<User>('/users', body);
  }

  updateUser(id: string, body: UpdateUserDto) {
    return this.put<User>('/users/:id', body, new UtilsHttpParamsRequest(undefined, { id }));
  }

  deleteUser(id: string) {
    return this.delete<void>('/users/:id', new UtilsHttpParamsRequest(undefined, { id }));
  }
}
```

### File Upload với Progress Tracking

```typescript
uploadAvatar(userId: string, file: File, onProgress?: (percent: number) => void) {
  return this.sendWithFile<{ url: string }>(
    '/users/:userId/avatar',
    new UtilsHttpParamsRequest(undefined, { userId }),
    {
      method: 'POST',
      bodyData: { file },
      keepFileOfBody: true,
      processUpload: onProgress ? ({ percent }) => onProgress(percent) : undefined,
    }
  );
}
```

### Caching với IndexedDB

```typescript
getUsersCached(params?: any) {
  const observable = this.get<User[]>('/users', new UtilsHttpParamsRequest(undefined, params));
  return this.cacheIndexDB(observable, 'users-list', [params], 5 * 60 * 1000);
}

// Xóa cache sau mutation
async createUser(body: CreateUserDto) {
  const result = await lastValueFrom(this.post<User>('/users', body));
  await this.deleteCacheKeyStartWidth('users-list');
  return result;
}
```

## API

### Abstract Methods (bắt buộc implement)

| Method                                    | Returns               | Mô tả                                            |
| ----------------------------------------- | --------------------- | ------------------------------------------------ |
| `getOptions<T>(httpParams, contentType?)` | `{ headers, params }` | Inject auth token, Content-Type vào mỗi request  |
| `redirectToLogin()`                       | `void`                | Được gọi khi nhận 401 Unauthorized               |
| `replaceURLByPattern<T>(url, params)`     | `{ url, params }`     | Thay thế URL dynamic segments (`:id`, `:userId`) |

### Public Methods

| Method                                              | Returns                        | Mô tả                                |
| --------------------------------------------------- | ------------------------------ | ------------------------------------ |
| `get<T, P>(path, params?)`                          | `Observable<IHttpResponse<T>>` | HTTP GET                             |
| `post<T, P, B>(path, body, params?)`                | `Observable<IHttpResponse<T>>` | HTTP POST                            |
| `postUrlEndCode<T, P, B>(path, body, params?)`      | `Observable<IHttpResponse<T>>` | POST URL-encoded form                |
| `put<T, P, B>(path, body, params?)`                 | `Observable<IHttpResponse<T>>` | HTTP PUT                             |
| `patch<T, P>(path, body, params?)`                  | `Observable<IHttpResponse<T>>` | HTTP PATCH                           |
| `patchUrlEndCode<T, P>(path, body, params?)`        | `Observable<IHttpResponse<T>>` | PATCH URL-encoded form               |
| `delete<T, P>(path, params?)`                       | `Observable<IHttpResponse<T>>` | HTTP DELETE                          |
| `sendWithFile<T, P, B>(path, params, args)`         | `Observable<IHttpResponse<T>>` | Upload file với progress             |
| `cacheIndexDB<T>(obs, key, params, time?, reload?)` | `Observable<IHttpResponse<T>>` | Cache IndexedDB ✅                   |
| `deleteCacheKeyStartWidth(key)`                     | `Promise<void>`                | Xóa cache theo prefix                |
| `cacheResponseData<T>(...)`                         | `Observable<IHttpResponse<T>>` | **Deprecated** — dùng `cacheIndexDB` |

### Protected Properties (override trong subclass)

| Property                      | Type                   | Default     | Mô tả                          |
| ----------------------------- | ---------------------- | ----------- | ------------------------------ |
| `baseUrl`                     | `string`               | `''`        | Base URL của API               |
| `keyCacheUniqueByIdUserLogin` | `string`               | `''`        | User ID suffix cho cache key   |
| `ignoreRedirect401`           | `boolean?`             | `undefined` | Tắt auto redirect khi 401      |
| `isStartZeroPage`             | `boolean`              | `false`     | Convert page 1-based → 0-based |
| `observeResponse`             | `'response' \| 'body'` | `'body'`    | Mode nhận HTTP response        |

## Types

```typescript
interface IHttpResponse<T> {
  code: number;
  message?: string;
  data?: T;
  paging?: IPaging;
  feCloneCode?: any;
  statusCodeHttp?: number;
}

interface IPaging {
  page?: number;
  per_page?: number;
  total_items?: number;
  total_pages?: number;
  before?: string;
  after?: string;
  previous?: string;
  next?: string;
}

interface IHttpProcessUpload {
  loaded: number;
  total: number;
  percent: number;
}
```

## Lưu ý quan trọng

- **Không inject trực tiếp** — class là abstract, phải extend
- **3 abstract methods** phải implement: `getOptions`, `redirectToLogin`, `replaceURLByPattern`
- **`cacheIndexDB` > `cacheResponseData`** — `cacheResponseData` đã deprecated
- **`keyCacheUniqueByIdUserLogin`** — set user ID để tránh cache conflict giữa các user
- **`sendWithFile` dùng XHR native** — không phải Angular HttpClient (để có progress events)
- **Response normalization** hỗ trợ nhiều backend format: REST standard, Spring Boot, cursor-based pagination