diff --git a/docs/reference/plugins/tanstack-query.mdx b/docs/reference/plugins/tanstack-query.mdx index 9f54ef75..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). @@ -33,8 +34,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 +182,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 +374,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 +546,91 @@ 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: ` + Create + @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, + } + }); + + //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', + 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/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'; +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()) { + Load More + } + `, + 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, + }; + }, + }); +} + + +``` +
Filtered Posts