
TanStack Query Angular 分页示例全解析keepPreviousData 分页与预取实战【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query本指南围绕仓库中的 Angular 分页示例examples/angular/pagination/README.md展开完整讲解如何在 Angular 应用中借助tanstack/angular-query-experimental实现翻页时旧数据保持可见、后台静默刷新、自动预取下一页的分页方案。读完本文你将掌握injectQuerykeepPreviousData的分页核心写法、effect驱动的下一页预取、基于isPlaceholderData的按钮禁用策略以及用 HTTP Interceptor 模拟分页接口的完整做法。示例概述与运行方式该示例位于 examples/angular/pagination其 README 给出的运行方式极简只有两步pnpm installpnpm start其中pnpm start实际执行的是 Angular CLI 的ng serve见 package.json。项目依赖 Angular 20 系列angular/core、angular/common均为^20.0.0与tanstack/angular-query-experimental: ^5.102.8同时使用rxjs: ^7.8.2将 HTTP 响应从 Observable 转为 Promise。在 Angular 20 中应用通过bootstrapApplication(AppComponent, appConfig)独立引导见 main.ts根组件 app.component.ts 仅渲染一个example /组件全部核心逻辑集中在example.component中。数据层设计服务与 Mock 拦截器分页服务projects.service.ts 定义了两个接口并暴露一个分页方法interface Project { id: number name: string } interface ProjectResponse { projects: ArrayProject hasMore: boolean } Injectable({ providedIn: root }) export class ProjectsService { readonly #http inject(HttpClient) getProjects(page: number) { return this.#http.getProjectResponse(/api/projects?page${page}) } }关键点接口返回结构包含projects当前页数据列表与hasMore是否还有下一页两个字段hasMore直接决定了前端下一页按钮的可用性与预取时机这是后端需要配合分页 UI 提供的契约。Mock 拦截器模拟真实分页接口为了让示例脱离真实后端即可运行projects-mock.interceptor.ts 用 Angular 的HttpInterceptorFn拦截/api/projects请求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)该拦截器从 URL 的page查询参数解析页码每页固定返回 10 条数据pageSize 10hasMore在page 9即第 08 页时为true共可翻 10 页并用delay(1000)模拟 1 秒网络延迟方便观察加载状态。拦截器通过 app.config.ts 注册export const appConfig: ApplicationConfig { providers: [ provideHttpClient(withInterceptors([projectsMockInterceptor]), withFetch()), provideTanStackQuery(new QueryClient(), withDevtools()), ], }这里同时完成两件关键配置用provideHttpClient挂载 mock 拦截器并启用 fetch 后端用provideTanStackQuery(new QueryClient(), withDevtools())向应用注入全局QueryClient并开启 Devtools。核心分页逻辑injectQuery keepPreviousData分页组件的核心在 example.component.ts首先定义查询readonly page signal(0) readonly query injectQuery(() ({ queryKey: [projects, this.page()], queryFn: () { return lastValueFrom(this.projectsService.getProjects(this.page())) }, placeholderData: keepPreviousData, staleTime: 5000, }))逐项拆解page signal(0)用 Angular 信号保存当前页码页码变化会通过闭包重新求值驱动queryKey与queryFn更新从而实现切换页码即切换查询。queryKey: [projects, this.page()]页码作为 queryKey 的组成部分。TanStack Query 的缓存以 queryKey 为维度因此每一页都会作为独立查询被缓存——这正是翻回上一页秒开的原因。queryFn中lastValueFromgetProjects返回 RxJSObservable而 TanStack Query 期望queryFn返回 Promise因此用lastValueFrom转换rxjs 的lastValueFrom会等待 Observable 发出最后一个值。placeholderData: keepPreviousData分页体验的灵魂。keepPreviousData的实现极其简单见 packages/query-core/src/utils.tsexport function keepPreviousDataT(previousData: T | undefined): T | undefined { return previousData }它作为占位数据函数被 TanStack Query 调用当新页面查询尚未返回时直接以上一页的data作为placeholderData继续渲染。效果就是模板注释中所说的——翻页过程中上一页数据保持可见直到下一页数据到达。注意keepPreviousData本身不执行任何网络请求它只是旧数据兜底策略与staleTime、后台重取相互配合。staleTime: 5000数据在 5 秒内被视为新鲜重复访问同一页时直接使用缓存、不触发重新请求配合分页缓存即可实现回翻旧页瞬时呈现同时后台静默刷新。模板层状态分支与按钮控制example.component.html 用 Angular 20 的if控制流语法渲染三种状态if (query.isPending()) { divLoading.../div } else if (query.isError()) { divError: {{ query.error().message }}/div } else if (query.isSuccess()) { div for (project of query.data().projects; track project.id) { p{{ project.name }}/p } /div }分页按钮的控制是另一个关键细节button (click)previousPage() [disabled]page() 0Previous Page/button button (click)nextPage() [disabled]query.isPlaceholderData() || !query.data()?.hasMore Next Page /button if (query.isFetching()) { spanLoading.../span }上一页按钮首页page() 0时禁用。下一页按钮两个禁用条件缺一不可——query.isPlaceholderData()表示当前展示的还是占位旧数据、下一页真实数据尚未到达此时不知道新页面的hasMore!query.data()?.hasMore表示已无更多数据。这正是模板开头描述的直到下一页游标cursor已知前下一页按钮被抑制。isFetching与isPending的区别由于placeholderData兜底翻页时isPending不会触发旧数据仍算有数据因此额外用isFetching显示后台加载中指示器。这三点旧页保持、按钮抑制、后台指示共同构成了流畅的分页体验。对应的翻页方法在组件类中实现previousPage() { this.page.update((currentPage) Math.max(currentPage - 1, 0)) } nextPage() { this.page.update((currentPage) this.query.data()?.hasMore ? currentPage 1 : currentPage, ) }nextPage在组件逻辑层也校验hasMore与模板的禁用逻辑形成双保险。纵深优化effect 驱动的下一页预取示例还演示了翻页之前先预取的进阶技巧通过 Angular 的effect实现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) } }) })工作原理与要点effect会追踪内部读取的响应式依赖——query.data()、query.isPlaceholderData()和page()。任一变化都会重新运行副作用。预取条件当前数据不是占位数据!isPlaceholderData且hasMore为真即确认确实还有下一页后再预取避免无意义的请求。预取动作放在untracked(() { ... })中执行queryClient.query(...)内部对page()的读取构造newPage已在外部完成以及调用结果都不会再被 effect 追踪防止预取引发的状态更新再次触发 effect 造成死循环。使用queryClient.query(...)而非injectQuery主动向缓存写入下一页数据返回的 Promise 用catch(noop)静默处理失败不污染预取路径。预取按页独立缓存。用户真正翻到下一页时数据已在缓存中配合staleTime: 5000可直接秒开若预取数据已过期则触发后台静默重新请求与旧页回翻体验一致。从源码结构看queryClient.query等价于一次不带订阅的查询挂载将结果写入以[projects, newPage]为 key 的缓存条目后续injectQuery使用相同 queryKey 时即可命中。这种服务端游标预取模式非常适合无限滚动、分页列表等场景。完整链路小结把上述环节串起来一次翻页操作的完整链路为点击 Next Page →nextPage()更新page信号injectQuery闭包重新求值queryKey 变为[projects, 新页码]placeholderData: keepPreviousData让上一页数据继续渲染同时后台发起新请求模板通过isFetching提示加载中新数据到达后替换渲染isPlaceholderData变为false下一页按钮恢复可点与此同时effect侦测到data/isPlaceholderData变化若hasMore为真则通过queryClient.query预取再下一页翻回旧页时命中[projects, 旧页码]缓存瞬时呈现超出staleTime则在后台静默刷新。该示例所依赖的keepPreviousData、injectQuery、QueryClient等均出自 packages/angular-query-experimental/src 与 packages/query-core/src 的正式实现Angular 版本的用法与其他框架React/Solid/Vue保持一致的分页心智模型。若需在生产中落地可将 Mock 拦截器替换为真实 REST 接口并沿用hasMore分页契约即可无缝切换。【免费下载链接】query Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考