From f1cef38da1d259a6ba679ec68b99e1d1af11131f Mon Sep 17 00:00:00 2001 From: Philipp Aleksovski Date: Mon, 11 Aug 2025 20:13:30 +0200 Subject: [PATCH 1/2] updated-docs --- docs/reference/plugins/tanstack-query.mdx | 229 +++++++++++++++++++++- 1 file changed, 227 insertions(+), 2 deletions(-) diff --git a/docs/reference/plugins/tanstack-query.mdx b/docs/reference/plugins/tanstack-query.mdx index 9f54ef75..05779614 100644 --- a/docs/reference/plugins/tanstack-query.mdx +++ b/docs/reference/plugins/tanstack-query.mdx @@ -33,8 +33,8 @@ npm install --save-dev @zenstackhq/tanstack-query | Name | Type | Description | Required | Default | | -------- | ------- | ------------------------------------------------------- | -------- | ------- | | output | String | Output directory (relative to the path of ZModel) | Yes | | -| target | String | Target framework to generate for. Choose from "react", "vue", and "svelte". | Yes | | -| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". | No | v5 | +| target | String | Target framework to generate for. Choose from "react", "vue", "svelte", "angular". | Yes | | +| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". Angular supports only v5 | No | v5 | | portable | Boolean | Include TypeScript types needed to compile the generated code in the output directory. Useful when you output into another project that doesn't reference Prisma and ZenStack. You'll still need to install the "@zenstackhq/tanstack-query" package in that project. | No | false | ### Hooks Signature @@ -181,6 +181,40 @@ provideHooksContext({ ``` + + +```typescript title='app.config.ts' +import { + provideTanStackQuery, + QueryClient, +} from '@tanstack/angular-query-experimental'; +import { provideAngularQueryContext } from '@/lib/hooks'; +import type { FetchFn } from '@zenstackhq/tanstack-query/runtime'; + +const myFetch: FetchFn = (url, options) => { + options = options ?? {}; + options.headers = { + ...options.headers, + 'x-my-custom-header': 'hello world', + }; + return fetch(url, options); +}; + +export const appConfig: ApplicationConfig = { + providers: [ + provideTanStackQuery(new QueryClient()), + provideAngularQueryContext({ + endpoint: 'http://localhost:3000/v1/api/rpc', + fetch: myFetch, + logging: true, + }), + ], +}; + +``` + + + :::info Notes about Next.js app router @@ -339,6 +373,44 @@ export default config; + + + +```typescript title='src/app/posts/posts.component.ts' + +import { + ApplicationConfig, + provideBrowserGlobalErrorListeners, + provideZonelessChangeDetection, +} from '@angular/core'; +import { provideRouter, withComponentInputBinding } from '@angular/router'; +import { routes } from './app.routes'; +import { provideAngularQueryContext } from '@/lib/hooks'; +import { + provideTanStackQuery, + QueryClient, + withDevtools, +} from '@tanstack/angular-query-experimental'; + + +export const appConfig: ApplicationConfig = { + providers: [ + provideBrowserGlobalErrorListeners(), + provideZonelessChangeDetection(), + provideTanStackQuery(new QueryClient(), withDevtools()), + provideAngularQueryContext({ + endpoint: 'http://localhost:3000/v1/api/rpc', + }), + provideRouter(routes, withComponentInputBinding()), + ], +}; + + +``` + + + + #### Using Query and Mutation Hooks @@ -473,6 +545,92 @@ const { data: posts } = useFindManyPost(queryParams); ``` + + + + +```typescript title='src/app/posts/posts.component.ts' +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + input, + signal, +} from '@angular/core'; +import { useCreatePost, useFindManyPost } from '@lib/hooks/generatedAPI'; + +@Component({ + selector: 'app-posts-component', + standalone: true, + imports: [CommonModule], + template: `
+ + @if (posts.data()) { +
    + @for (post of posts.data(); track post.id) { +
  • {{ post.title }} by {{ post.author.email }}
  • + } +
+ } +
+
+

Filtered Posts

+ + @if (filteredPosts.data()) { +
    + @for (post of filteredPosts.data(); track post.id) { +
  • {{ post.title }} by {{ post.author.email }}
  • + } +
+ } +
`, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PostsComponent { + id = input.required(); + search = signal(''); + + posts = useFindManyPost({ + include: { author: true }, + orderBy: { createdAt: 'desc' }, + }); + + filteredPostsArgs = computed(() => { + const search = this.search(); + + return { + where: { title: { contains: search } }, + include: { author: true }, + orderBy: { createdAt: 'desc' }, + } as const; //as const is important for correct type inference! + }); + + //For Reactivity in angular we have to pass the signal as callback + filteredPosts = useFindManyPost(() => this.filteredPostsArgs()); + + create = useCreatePost(); + + onCreatePost() { + this.create.mutate({ + data: { + title: 'My awesome post', + content: 'This is the content of my awesome post.', + authorId: this.id(), + }, + }); + } +} + +``` +
@@ -679,6 +837,73 @@ Here's a quick example of using infinite query to load a list of posts with infi ``` + + + + +Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/examples/svelte/load-more-infinite-scroll) for more details. + +```ts title='src/app/posts/posts.component.ts' +import { CommonModule } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + computed, + signal, +} from '@angular/core'; +import { useInfiniteFindManyPost } from '@/lib/hooks'; + +@Component({ + selector: 'app-posts-component', + standalone: true, + imports: [CommonModule], + template: ` +
+ @if (postsInfinite.data(); as data) { +
    + @for (posts of data.pages; track $index) { + @for (post of posts; track post.id) { +
  • {{ post.title }} by {{ post.author.email }}
  • + } + } +
+ } +
+ + @if (postsInfinite.hasNextPage()) { + + } + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PostsComponent { + PAGE_SIZE = signal(10); + + fetchArgs = computed(() => { + return { + include: { author: true }, + orderBy: { createdAt: 'desc' as const }, + take: this.PAGE_SIZE(), + }; + }); + + postsInfinite = useInfiniteFindManyPost(() => this.fetchArgs(), { + getNextPageParam: (lastPage, pages) => { + if (lastPage.length < this.PAGE_SIZE()) { + return undefined; + } + const fetched = pages.flatMap((item) => item).length; + return { + ...this.fetchArgs, + skip: fetched, + }; + }, + }); +} + + +``` +
From 0801fc5126cbcd6ee49c36bc966e6b49a61c2f5e Mon Sep 17 00:00:00 2001 From: Philipp Aleksovski Date: Tue, 12 Aug 2025 08:44:07 +0200 Subject: [PATCH 2/2] implemented-code-rabbit-suggestions --- docs/reference/plugins/tanstack-query.mdx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference/plugins/tanstack-query.mdx b/docs/reference/plugins/tanstack-query.mdx index 05779614..b3f40acf 100644 --- a/docs/reference/plugins/tanstack-query.mdx +++ b/docs/reference/plugins/tanstack-query.mdx @@ -16,7 +16,8 @@ import FineGrainedOptimistic from './_fine-grained-optimistic.md'; If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](./swr) plugin. ::: -The `@zenstackhq/tanstack-query` plugin generates [Tanstack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React and Svelte. Vue support is coming soon. +The `@zenstackhq/tanstack-query` plugin generates [TanStack Query](https://tanstack.com/query/latest) hooks that call into the CRUD services provided by the [server adapters](../../category/server-adapters). The plugin currently supports React, Vue, Svelte, and Angular (v5 only). + The hooks syntactically mirror the APIs of a standard Prisma client, including the function names and shapes of parameters (hooks directly use types generated by Prisma). @@ -34,7 +35,7 @@ npm install --save-dev @zenstackhq/tanstack-query | -------- | ------- | ------------------------------------------------------- | -------- | ------- | | output | String | Output directory (relative to the path of ZModel) | Yes | | | target | String | Target framework to generate for. Choose from "react", "vue", "svelte", "angular". | Yes | | -| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". Angular supports only v5 | No | v5 | +| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". Angular supports only "v5" | No | v5 | | portable | Boolean | Include TypeScript types needed to compile the generated code in the output directory. Useful when you output into another project that doesn't reference Prisma and ZenStack. You'll still need to install the "@zenstackhq/tanstack-query" package in that project. | No | false | ### Hooks Signature @@ -609,8 +610,8 @@ export class PostsComponent { return { where: { title: { contains: search } }, include: { author: true }, - orderBy: { createdAt: 'desc' }, - } as const; //as const is important for correct type inference! + orderBy: { createdAt: 'desc' } as const, + } }); //For Reactivity in angular we have to pass the signal as callback @@ -622,7 +623,6 @@ export class PostsComponent { this.create.mutate({ data: { title: 'My awesome post', - content: 'This is the content of my awesome post.', authorId: this.id(), }, }); @@ -841,7 +841,7 @@ Here's a quick example of using infinite query to load a list of posts with infi -Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v4/docs/svelte/examples/svelte/load-more-infinite-scroll) for more details. +Here's a quick example of using infinite query to load a list of posts with infinite pagination. See [Tanstack Query documentation](https://tanstack.com/query/v5/docs/framework/angular/examples/infinite-query-with-max-pages) for more details. ```ts title='src/app/posts/posts.component.ts' import { CommonModule } from '@angular/common'; @@ -894,7 +894,7 @@ export class PostsComponent { } const fetched = pages.flatMap((item) => item).length; return { - ...this.fetchArgs, + ...this.fetchArgs(), skip: fetched, }; },