Spoosh
Injects

injectInfiniteRead

Bidirectional paginated data fetching with infinite scroll support using Signals

Bidirectional paginated data fetching with infinite scroll support. Returns Angular Signals for reactive state binding.

Basic Usage

@Component({
  selector: "app-post-list",
  template: `
    @if (canFetchPrev()) {
      <button (click)="fetchPrev()" [disabled]="fetchingPrev()">
        Load Previous
      </button>
    }

    @for (post of data(); track post.id) {
      <app-post-card [post]="post" />
    }

    @if (canFetchNext()) {
      <button (click)="fetchNext()" [disabled]="fetchingNext()">
        Load More
      </button>
    }
  `,
})
export class PostListComponent {
  private posts = injectInfiniteRead(
    (api) => api("posts/paginated").GET({ query: { page: 1 } }),
    {
      canFetchNext: ({ response }) => response?.meta.hasMore ?? false,
      nextPageRequest: ({ response, request }) => ({
        query: { ...request.query, page: (response?.meta.page ?? 0) + 1 },
      }),
      merger: (allResponses) => allResponses.flatMap((r) => r.items),
      canFetchPrev: ({ response }) => (response?.meta.page ?? 1) > 1,
      prevPageRequest: ({ response, request }) => ({
        query: { ...request.query, page: (response?.meta.page ?? 2) - 1 },
      }),
    }
  );

  data = this.posts.data;
  loading = this.posts.loading;
  canFetchNext = this.posts.canFetchNext;
  canFetchPrev = this.posts.canFetchPrev;
  fetchNext = this.posts.fetchNext;
  fetchPrev = this.posts.fetchPrev;
  fetchingNext = this.posts.fetchingNext;
  fetchingPrev = this.posts.fetchingPrev;
}

Options

OptionTypeRequiredDescription
canFetchNext(ctx) => booleanYesCheck if next page exists
nextPageRequest(ctx) => Partial<TRequest>YesBuild request for next page
merger(allResponses) => TItem[]YesMerge all responses into items
canFetchPrev(ctx) => booleanNoCheck if previous page exists
prevPageRequest(ctx) => Partial<TRequest>NoBuild request for previous page
enabledboolean | Signal<bool>NoWhether to fetch automatically

Context Object

type Context<TData, TRequest> = {
  response: TData | undefined;
  allResponses: TData[];
  request: TRequest;
};

Returns

PropertyTypeDescription
dataSignal<TItem[] | undefined>Merged items from all responses
allResponsesSignal<TData[] | undefined>Array of all raw responses
loadingSignal<boolean>True during initial load
fetchingSignal<boolean>True during any fetch
fetchingNextSignal<boolean>True while fetching next page
fetchingPrevSignal<boolean>True while fetching previous
canFetchNextSignal<boolean>Whether next page exists
canFetchPrevSignal<boolean>Whether previous page exists
fetchNext() => Promise<void>Fetch the next page
fetchPrev() => Promise<void>Fetch the previous page
refetch() => Promise<void>Refetch all pages
abort() => voidAbort current request
errorSignal<TError | undefined>Error if request failed
metaSignal<object>Plugin-provided metadata

On this page