snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s

This commit is contained in:
upstream-mirror
2026-09-16 01:13:08 +05:00
commit 3a013ccdfe
3570 changed files with 490194 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
}

View File

@@ -0,0 +1,6 @@
# TanStack Query Angular pagination example
To run this example:
- `pnpm install`
- `pnpm start`

View File

@@ -0,0 +1,130 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"newProjectRoot": "projects",
"projects": {
"pagination": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/pagination",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico"],
"styles": [],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "pagination:build:production"
},
"development": {
"buildTarget": "pagination:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "pagination:build"
}
}
}
}
},
"schematics": {
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
}
}
}

View File

@@ -0,0 +1,27 @@
{
"name": "@tanstack/query-example-angular-pagination",
"private": true,
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"dependencies": {
"@angular/common": "^20.0.0",
"@angular/compiler": "^20.0.0",
"@angular/core": "^20.0.0",
"@angular/platform-browser": "^20.0.0",
"@tanstack/angular-query-experimental": "^5.102.8",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
"zone.js": "0.15.0"
},
"devDependencies": {
"@angular/build": "^20.0.0",
"@angular/cli": "^20.0.0",
"@angular/compiler-cli": "^20.0.0",
"typescript": "5.8.3"
}
}

View File

@@ -0,0 +1,40 @@
import { HttpResponse } from '@angular/common/http'
import { delay, of } from 'rxjs'
import type { Observable } from 'rxjs'
import type { HttpEvent, HttpInterceptorFn } from '@angular/common/http'
export const projectsMockInterceptor: HttpInterceptorFn = (
req,
next,
): Observable<HttpEvent<any>> => {
const { url } = req
if (url.includes('/api/projects')) {
const page = parseInt(
new URLSearchParams(req.url.split('?')[1]).get('page') || '0',
10,
)
const pageSize = 10
const projects = Array(pageSize)
.fill(0)
.map((_, i) => {
const id = page * pageSize + (i + 1)
return {
name: 'Project ' + id,
id,
}
})
return of(
new HttpResponse({
status: 200,
body: {
projects,
hasMore: page < 9,
},
}),
).pipe(delay(1000))
}
return next(req)
}

View File

@@ -0,0 +1,10 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { ExampleComponent } from './components/example.component'
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-root',
template: `<example />`,
imports: [ExampleComponent],
})
export class AppComponent {}

View File

@@ -0,0 +1,19 @@
import {
provideHttpClient,
withFetch,
withInterceptors,
} from '@angular/common/http'
import {
QueryClient,
provideTanStackQuery,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { projectsMockInterceptor } from './api/projects-mock.interceptor'
import type { ApplicationConfig } from '@angular/core'
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withInterceptors([projectsMockInterceptor]), withFetch()),
provideTanStackQuery(new QueryClient(), withDevtools()),
],
}

View File

@@ -0,0 +1,39 @@
<div>
<p>
In this example, each page of data remains visible as the next page is
fetched. The buttons and capability to proceed to the next page are also
suppressed until the next page cursor is known. Each page is cached as a
normal query too, so when going to previous pages, you'll see them
instantaneously while they are also refetched invisibly in the background.
</p>
@if (query.isPending()) {
<div>Loading...</div>
} @else if (query.isError()) {
<div>Error: {{ query.error().message }}</div>
} @else if (query.isSuccess()) {
<div>
@for (project of query.data().projects; track project.id) {
<p>{{ project.name }}</p>
}
</div>
}
<div>Current Page: {{ page() + 1 }}</div>
<button (click)="previousPage()" [disabled]="page() === 0">
Previous Page
</button>
<button
(click)="nextPage()"
[disabled]="query.isPlaceholderData() || !query.data()?.hasMore"
>
Next Page
</button>
<!-- Since the last page's data potentially sticks around between page requests, -->
<!-- we can use `isFetching` to show a background loading -->
<!-- indicator since our `status === 'pending'` state won't be triggered -->
@if (query.isFetching()) {
<span>Loading...</span>
}
</div>

View File

@@ -0,0 +1,66 @@
import {
ChangeDetectionStrategy,
Component,
effect,
inject,
signal,
untracked,
} from '@angular/core'
import {
QueryClient,
injectQuery,
keepPreviousData,
noop,
} from '@tanstack/angular-query-experimental'
import { lastValueFrom } from 'rxjs'
import { ProjectsService } from '../services/projects.service'
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'example',
templateUrl: './example.component.html',
})
export class ExampleComponent {
readonly queryClient = inject(QueryClient)
readonly projectsService = inject(ProjectsService)
readonly page = signal(0)
readonly query = injectQuery(() => ({
queryKey: ['projects', this.page()],
queryFn: () => {
return lastValueFrom(this.projectsService.getProjects(this.page()))
},
placeholderData: keepPreviousData,
staleTime: 5000,
}))
readonly prefetchEffect = effect(() => {
const data = this.query.data()
const isPlaceholderData = this.query.isPlaceholderData()
const newPage = this.page() + 1
untracked(() => {
if (!isPlaceholderData && data?.hasMore) {
void this.queryClient
.query({
queryKey: ['projects', newPage],
queryFn: () =>
lastValueFrom(this.projectsService.getProjects(newPage)),
})
.catch(noop)
}
})
})
previousPage() {
this.page.update((currentPage) => {
return Math.max(currentPage - 1, 0)
})
}
nextPage() {
this.page.update((currentPage) => {
return this.query.data()?.hasMore ? currentPage + 1 : currentPage
})
}
}

View File

@@ -0,0 +1,23 @@
import { HttpClient } from '@angular/common/http'
import { Injectable, inject } from '@angular/core'
interface Project {
id: number
name: string
}
interface ProjectResponse {
projects: Array<Project>
hasMore: boolean
}
@Injectable({
providedIn: 'root',
})
export class ProjectsService {
readonly #http = inject(HttpClient)
getProjects(page: number) {
return this.#http.get<ProjectResponse>(`/api/projects?page=${page}`)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>TanStack Query Angular pagination example</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -0,0 +1,5 @@
import { bootstrapApplication } from '@angular/platform-browser'
import { appConfig } from './app/app.config'
import { AppComponent } from './app/app.component'
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err))

View File

@@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": ["src/main.ts"],
"include": ["src/**/*.d.ts"]
}

View File

@@ -0,0 +1,31 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist/out-tsc",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"esModuleInterop": true,
"sourceMap": true,
"declaration": false,
"experimentalDecorators": true,
"moduleResolution": "Bundler",
"importHelpers": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,
"lib": ["ES2022", "dom"]
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictStandalone": true,
"strictTemplates": true
}
}