npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/services-base-request-abstract

v0.2.357-4

Published

> Abstract base class cung cấp toàn bộ infrastructure cho HTTP requests trong Angular: chuẩn hóa response, xử lý lỗi tự động, caching IndexedDB, upload file với progress tracking và chuyển đổi page 0-based/1-based.

Readme

@libs-ui/services-base-request-abstract

Abstract base class cung cấp toàn bộ infrastructure cho HTTP requests trong Angular: chuẩn hóa response, xử lý lỗi tự động, caching IndexedDB, upload file với progress tracking và chuyển đổi page 0-based/1-based.

Giới thiệu

LibsUiBaseRequestAbstractService là abstract class trung tâm dùng để xây dựng tầng API service trong ứng dụng Angular. Class này đóng gói toàn bộ logic gọi HTTP (GET, POST, PUT, PATCH, DELETE), chuẩn hóa response từ nhiều format backend khác nhau, tự động redirect khi nhận 401 Unauthorized, hỗ trợ caching IndexedDB phân tầng theo user, và upload file native XHR với progress tracking.

Thay vì mỗi service tự viết logic xử lý lỗi và chuẩn hóa response, ta chỉ cần extend class này và implement 3 abstract methods bắt buộc.

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ợ REST standard, Spring Boot (totalElements, totalPages), cursor-based pagination
  • ✅ Pagination chuẩn hóa từ nhiều backend format về IPaging thống nhất
  • ✅ Error handling: 401 → auto redirect login, phân biệt lỗi 4xx vs 5xx
  • ✅ Caching với IndexedDB (cacheIndexDB) — phân tầng theo user ID
  • ✅ Page zero conversion (isStartZeroPage) — UI 1-based tự động chuyển sang API 0-based
  • ✅ URL dynamic segments — thay thế :id, :userId trong URL tự động qua replaceURLByPattern
  • observeResponse — chọn mode nhận full HTTP response hoặc chỉ body

Khi nào sử dụng

  • Khi cần tạo một API service mới để gọi backend REST API
  • Khi muốn thống nhất cách xử lý auth token, error handling và response format toàn project
  • Khi cần caching dữ liệu API theo từng user (IndexedDB)
  • Khi cần upload file với progress bar (không dùng được XHR native thuần)
  • Khi backend trả về nhiều format pagination khác nhau (cần chuẩn hóa về IPaging)

Cài đặt

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

Import

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

Ví dụ sử dụng

Ví dụ 1 — Tạo ApiService gốc (implement 3 abstract methods bắt buộc)

import { Injectable } from '@angular/core';
import { HttpHeaders, HttpParams } from '@angular/common/http';
import { inject } from '@angular/core';
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';

  private readonly router = inject(Router);

  constructor() {
    super();
    // Gán user ID để phân tầng cache theo từng user login
    this.keyCacheUniqueByIdUserLogin = sessionStorage.getItem('userId') || '';
  }

  // [1] Inject auth header và content-type cho mọi request
  protected getOptions<T>(params: UtilsHttpParamsRequest<T>, contentType?: string) {
    const token = sessionStorage.getItem('accessToken');
    return {
      headers: new HttpHeaders({
        'Content-Type': contentType || 'application/json',
        ...(token ? { Authorization: `Bearer ${token}` } : {}),
      }),
      params: params as unknown as HttpParams,
    };
  }

  // [2] Được gọi tự động khi server trả về 401 Unauthorized
  protected redirectToLogin(): void {
    this.router.navigate(['/login']);
  }

  // [3] Thay thế URL dynamic segments — `:id`, `:userId`, `:orgId`...
  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) as UtilsHttpParamsRequest<T>;
      }
    }
    return { url: resultUrl, params };
  }
}

Ví dụ 2 — Feature service kế thừa ApiService (CRUD đầy đủ)

import { Injectable } from '@angular/core';
import { UtilsHttpParamsRequest } from '@libs-ui/utils';
import { ApiService } from './api.service';

export type T_user = {
  id: string;
  name: string;
  email: string;
  status: string;
};

export type T_user_list_params = {
  page?: number;
  size?: number;
  search?: string;
  status?: string;
};

export type T_user_create_body = {
  name: string;
  email: string;
  password: string;
};

export type T_user_update_body = {
  name?: string;
  email?: string;
};

@Injectable({ providedIn: 'root' })
export class UserApiService extends ApiService {
  getList(params: T_user_list_params) {
    return this.get<T_user[]>('/users', new UtilsHttpParamsRequest(undefined, { fromObject: params }));
  }

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

  create(body: T_user_create_body) {
    return this.post<T_user>('/users', body);
  }

  update(id: string, body: T_user_update_body) {
    return this.put<T_user>('/users/:id', body, new UtilsHttpParamsRequest(undefined, { fromObject: { id } }));
  }

  partialUpdate(id: string, body: Partial<T_user_update_body>) {
    return this.patch<T_user>('/users/:id', body, new UtilsHttpParamsRequest(undefined, { fromObject: { id } }));
  }

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

Ví dụ 3 — Caching dữ liệu với IndexedDB

import { Injectable } from '@angular/core';
import { lastValueFrom } from 'rxjs';
import { UtilsHttpParamsRequest } from '@libs-ui/utils';
import { ApiService } from './api.service';
import { T_user, T_user_list_params, T_user_create_body } from './user-api.service';

@Injectable({ providedIn: 'root' })
export class UserCachedApiService extends ApiService {
  // Cache danh sách 5 phút (300000ms)
  getListCached(params: T_user_list_params) {
    const observable = this.get<T_user[]>('/users', new UtilsHttpParamsRequest(undefined, { fromObject: params }));
    return this.cacheIndexDB(observable, 'users-list', [params], 5 * 60 * 1000);
  }

  // Xóa cache sau khi tạo mới để lần sau lấy dữ liệu mới nhất
  async create(body: T_user_create_body) {
    const result = await lastValueFrom(this.post<T_user>('/users', body));
    await this.deleteCacheKeyStartWidth('users-list');
    return result;
  }

  // Bắt buộc lấy dữ liệu mới (bỏ qua cache hiện có)
  getListForceRefresh(params: T_user_list_params) {
    const observable = this.get<T_user[]>('/users', new UtilsHttpParamsRequest(undefined, { fromObject: params }));
    return this.cacheIndexDB(observable, 'users-list', [params], 5 * 60 * 1000, true);
  }
}

Ví dụ 4 — Upload file với progress tracking

import { Injectable } from '@angular/core';
import { UtilsHttpParamsRequest } from '@libs-ui/utils';
import { ApiService } from './api.service';

export type T_upload_result = { url: string; fileName: string };

@Injectable({ providedIn: 'root' })
export class FileUploadApiService extends ApiService {
  uploadAvatar(userId: string, file: File, onProgress?: (percent: number) => void) {
    return this.sendWithFile<T_upload_result>(
      '/users/:userId/avatar',
      new UtilsHttpParamsRequest(undefined, { fromObject: { userId } }),
      {
        method: 'POST',
        bodyData: { file },
        keepFileOfBody: true,
        processUpload: onProgress
          ? ({ percent }) => onProgress(percent)
          : undefined,
      }
    );
  }

  uploadMultipleFiles(folderId: string, files: File[], onProgress?: (percent: number) => void) {
    return this.sendWithFile<T_upload_result[]>(
      '/folders/:folderId/files',
      new UtilsHttpParamsRequest(undefined, { fromObject: { folderId } }),
      {
        method: 'POST',
        bodyData: { files },
        keepFileOfBody: true,
        processUpload: onProgress
          ? ({ percent }) => onProgress(percent)
          : undefined,
      }
    );
  }
}

Ví dụ 5 — API 0-based pagination (isStartZeroPage)

Khi backend nhận page bắt đầu từ 0 nhưng UI hiển thị bắt đầu từ 1:

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

@Injectable({ providedIn: 'root' })
export class ApiZeroPageService extends LibsUiBaseRequestAbstractService {
  protected override baseUrl = 'https://api.spring-boot-backend.com';
  // UI gửi page=1 → service tự trừ 1 → backend nhận page=0
  protected override isStartZeroPage = true;

  private readonly router = inject(Router);

  protected getOptions<T>(params: UtilsHttpParamsRequest<T>, contentType?: string) {
    return {
      headers: new HttpHeaders({ 'Content-Type': contentType || 'application/json' }),
      params: params as unknown as HttpParams,
    };
  }

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

  protected replaceURLByPattern<T>(url: string, params: UtilsHttpParamsRequest<T>) {
    return { url, params };
  }
}

Ví dụ 6 — Form URL-encoded (postUrlEndCode)

Dùng khi backend yêu cầu Content-Type: application/x-www-form-urlencoded:

import { Injectable } from '@angular/core';
import { UtilsHttpParamsRequest } from '@libs-ui/utils';
import { ApiService } from './api.service';

export type T_login_body = { username: string; password: string };
export type T_auth_response = { accessToken: string; refreshToken: string };

@Injectable({ providedIn: 'root' })
export class AuthApiService extends ApiService {
  login(body: T_login_body) {
    // Gửi form URL-encoded thay vì JSON
    return this.postUrlEndCode<T_auth_response>('/auth/login', body);
  }

  refreshToken(refreshToken: string) {
    return this.postUrlEndCode<T_auth_response>('/auth/refresh', { refreshToken });
  }
}

Methods

Abstract Methods (bắt buộc implement trong subclass)

| Method | Signature | Mô tả | |---|---|---| | getOptions | <T>(httpParams: UtilsHttpParamsRequest<T>, contentType?: string) => { headers: HttpHeaders; params: HttpParams } | Inject auth token, Content-Type vào mọi request | | redirectToLogin | () => void | Được gọi tự động khi server trả về 401 Unauthorized | | replaceURLByPattern | <T>(url: string, params: UtilsHttpParamsRequest<T>) => { url: string; params: UtilsHttpParamsRequest<T> } | Thay thế URL dynamic segments (:id, :userId) từ params |

HTTP Methods

| Method | Signature | Trả về | Mô tả | |---|---|---|---| | get | <TypeResponse, TypeParams>(path, params?) | Observable<IHttpResponse<TypeResponse>> | HTTP GET | | post | <TypeResponse, TypeParams, TypeBody>(path, body, params?) | Observable<IHttpResponse<TypeResponse>> | HTTP POST JSON | | postUrlEndCode | <TypeResponse, TypeParams, TypeBody>(path, body, params?) | Observable<IHttpResponse<TypeResponse>> | POST application/x-www-form-urlencoded | | put | <TypeResponse, TypeParams, TypeBody>(path, body, params?) | Observable<IHttpResponse<TypeResponse>> | HTTP PUT JSON | | patch | <TypeResponse, TypeParams>(path, body, params?) | Observable<IHttpResponse<TypeResponse>> | HTTP PATCH JSON | | patchUrlEndCode | <TypeResponse, TypeParams>(path, body, params?) | Observable<IHttpResponse<TypeResponse>> | PATCH application/x-www-form-urlencoded | | delete | <TypeResponse, TypeParams>(path, params?) | Observable<IHttpResponse<TypeResponse>> | HTTP DELETE | | sendWithFile | <TypeResponse, TypeParams, TypeBody>(path, params, args) | Observable<IHttpResponse<TypeResponse>> | Upload file qua XHR native với progress tracking |

Cache Methods

| Method | Signature | Trả về | Mô tả | |---|---|---|---| | cacheIndexDB | <T>(observable, key, params, timeCache?, reloadCache?) | Observable<IHttpResponse<T>> | Cache response vào IndexedDB, tự động phân tầng theo user | | deleteCacheKeyStartWidth | (key: string) | Promise<void> | Xóa toàn bộ cache có key bắt đầu với prefix cho trước | | cacheResponseData | <T>(observable, key, params, timeCache?, reloadCache?) | Observable<IHttpResponse<T>> | Deprecated — dùng cacheIndexDB thay thế |

Utility Methods

| Method | Signature | Trả về | Mô tả | |---|---|---|---| | getResponseURL | (xhr: any) | string | Lấy URL thực tế sau redirect từ XHR object |

Protected Properties (override trong subclass)

| Property | Type | Default | Mô tả | |---|---|---|---| | baseUrl | string | '' | Base URL của API (bắt buộc set) | | keyCacheUniqueByIdUserLogin | string | '' | User ID suffix cho cache key — tránh conflict giữa các user | | ignoreRedirect401 | boolean \| undefined | undefined | Set true để tắt auto redirect khi nhận 401 | | isStartZeroPage | boolean | false | Set true khi backend dùng page 0-based, UI dùng 1-based | | ignoreAutoIncrementPageIsStartZeroPage | boolean | false | Set true để tắt auto increment page trong response khi isStartZeroPage = true | | observeResponse | 'response' \| 'body' | 'body' | Mode nhận HTTP response: 'body' chỉ nhận data, 'response' nhận full HTTP response |

Tham số của sendWithFile

| Tham số | Type | Bắt buộc | Mô tả | |---|---|---|---| | path | string | ✅ | Đường dẫn API (có thể chứa dynamic segments) | | params | UtilsHttpParamsRequest<TypeParams> | ✅ | Query params và URL path variables | | args.method | 'POST' \| 'PATCH' \| 'PUT' | ✅ | HTTP method cho upload | | args.bodyData | TypeBody \| FormData | ✅ | Dữ liệu body — plain object hoặc FormData có sẵn | | args.keepFileOfBody | boolean | ❌ | Set true để giữ nguyên File/Blob thay vì JSON.stringify | | args.keepArrayValue | boolean | ❌ | Set true để serialize array thành JSON string thay vì append từng phần tử | | args.processUpload | (process: IHttpProcessUpload) => void | ❌ | Callback nhận tiến độ upload |

Tham số của cacheIndexDB

| Tham số | Type | Bắt buộc | Mô tả | |---|---|---|---| | observable | Observable<IHttpResponse<T>> | ✅ | Observable HTTP call cần cache | | key | string | ✅ | Cache key prefix (không md5 trước) | | params | Array<any> | ✅ | Mảng tham số để tạo unique key (page, size, filter...) | | timeCache | number | ❌ | Thời gian cache tính bằng ms (mặc định: UtilsCache.CACHE_EXPIRE_TIME_DEFAULT) | | reloadCache | boolean | ❌ | Set true để xóa cache cũ và lấy dữ liệu mới nhất |

Types & Interfaces

import { IHttpResponse, IPaging, IHttpProcessUpload } from '@libs-ui/interfaces-types';
import { UtilsHttpParamsRequest, TYPE_OBSERVE_RESPONSE } from '@libs-ui/services-base-request-abstract';
// Response chuẩn hóa từ mọi HTTP method
type IHttpResponse<T = any, P = IPaging> = {
  code?: number;
  feCloneCode?: any; // code thực từ backend khi HTTP trả 200 nhưng business code khác
  message?: string;
  data?: T;
  paging?: P;
  [key: string]: unknown;
};

// Pagination chuẩn hóa từ nhiều backend format
interface IPaging {
  page?: number;
  per_page?: number;
  total_items?: number;
  total_pages?: number;
  before?: string;  // cursor-based pagination
  after?: string;   // cursor-based pagination
  previous?: string;
  next?: string;
  fake?: boolean;
}

// Progress upload
interface IHttpProcessUpload {
  loaded: number;   // bytes đã upload
  total: number;    // tổng bytes
  percent: number;  // phần trăm (0-100)
}

// Mode nhận HTTP response
type TYPE_OBSERVE_RESPONSE = 'response' | 'body';

Lưu ý quan trọng

⚠️ Class là abstract — không inject trực tiếp: Phải extend LibsUiBaseRequestAbstractService và implement đầy đủ 3 abstract methods: getOptions, redirectToLogin, replaceURLByPattern.

⚠️ 3 abstract methods là bắt buộc: Thiếu bất kỳ method nào sẽ gây lỗi TypeScript compile-time. Không thể dùng super() để bỏ qua.

⚠️ cacheIndexDB thay thế cacheResponseData: Method cacheResponseData đã deprecated (dùng Promise internally). Luôn dùng cacheIndexDB cho code mới — Observable-first, không block event loop.

⚠️ keyCacheUniqueByIdUserLogin phải set trước khi dùng cache: Nếu bỏ trống, cache không phân tầng theo user — user A sẽ thấy data của user B sau khi login. Gán user ID trong constructor của ApiService gốc.

⚠️ deleteCacheKeyStartWidth — không md5 key trước khi truyền: Method này tự md5 key nội bộ. Truyền vào key thô (cùng string đã dùng khi gọi cacheIndexDB).

⚠️ sendWithFile dùng XHR native: Không phải Angular HttpClient — để có onprogress event. Interceptor của Angular không áp dụng ở đây. Headers phải được set thủ công từ getOptions.

⚠️ isStartZeroPage và response paging: Khi bật isStartZeroPage, service tự trừ 1 trước khi gửi lên API và tự cộng 1 trong response trả về. Component chỉ cần làm việc với page 1-based nhất quán.

⚠️ postUrlEndCode / patchUrlEndCode: Tự động encode body thành URL-encoded format. Không truyền JSON body vào — truyền plain object, class sẽ tự encode.