snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Node.js",
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
|
||||
}
|
||||
6
examples/angular/pagination/README.md
Normal file
6
examples/angular/pagination/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# TanStack Query Angular pagination example
|
||||
|
||||
To run this example:
|
||||
|
||||
- `pnpm install`
|
||||
- `pnpm start`
|
||||
130
examples/angular/pagination/angular.json
Normal file
130
examples/angular/pagination/angular.json
Normal 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": "."
|
||||
}
|
||||
}
|
||||
}
|
||||
27
examples/angular/pagination/package.json
Normal file
27
examples/angular/pagination/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
10
examples/angular/pagination/src/app/app.component.ts
Normal file
10
examples/angular/pagination/src/app/app.component.ts
Normal 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 {}
|
||||
19
examples/angular/pagination/src/app/app.config.ts
Normal file
19
examples/angular/pagination/src/app/app.config.ts
Normal 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()),
|
||||
],
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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}`)
|
||||
}
|
||||
}
|
||||
BIN
examples/angular/pagination/src/favicon.ico
Normal file
BIN
examples/angular/pagination/src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
13
examples/angular/pagination/src/index.html
Normal file
13
examples/angular/pagination/src/index.html
Normal 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>
|
||||
5
examples/angular/pagination/src/main.ts
Normal file
5
examples/angular/pagination/src/main.ts
Normal 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))
|
||||
10
examples/angular/pagination/tsconfig.app.json
Normal file
10
examples/angular/pagination/tsconfig.app.json
Normal 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"]
|
||||
}
|
||||
31
examples/angular/pagination/tsconfig.json
Normal file
31
examples/angular/pagination/tsconfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user