Skip to content

Commit b28a2d2

Browse files
authored
document TanStack Query plugin for Angular (#483)
* updated-docs * implemented-code-rabbit-suggestions
1 parent 98b54c0 commit b28a2d2

File tree

1 file changed

+228
-3
lines changed

1 file changed

+228
-3
lines changed

docs/reference/plugins/tanstack-query.mdx

Lines changed: 228 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import FineGrainedOptimistic from './_fine-grained-optimistic.md';
1616
If you're looking for generating hooks for [SWR](https://swr.vercel.app/), please checkout the [`@zenstackhq/swr`](./swr) plugin.
1717
:::
1818

19-
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.
19+
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).
20+
2021

2122
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).
2223

@@ -33,8 +34,8 @@ npm install --save-dev @zenstackhq/tanstack-query
3334
| Name | Type | Description | Required | Default |
3435
| -------- | ------- | ------------------------------------------------------- | -------- | ------- |
3536
| output | String | Output directory (relative to the path of ZModel) | Yes | |
36-
| target | String | Target framework to generate for. Choose from "react", "vue", and "svelte". | Yes | |
37-
| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". | No | v5 |
37+
| target | String | Target framework to generate for. Choose from "react", "vue", "svelte", "angular". | Yes | |
38+
| version | String | Version of TanStack Query to generate for. Choose from "v4" and "v5". Angular supports only "v5" | No | v5 |
3839
| 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 |
3940

4041
### Hooks Signature
@@ -181,6 +182,40 @@ provideHooksContext({
181182
```
182183
</TabItem>
183184
185+
<TabItem value="angular" label="Angular">
186+
187+
```typescript title='app.config.ts'
188+
import {
189+
provideTanStackQuery,
190+
QueryClient,
191+
} from '@tanstack/angular-query-experimental';
192+
import { provideAngularQueryContext } from '@/lib/hooks';
193+
import type { FetchFn } from '@zenstackhq/tanstack-query/runtime';
194+
195+
const myFetch: FetchFn = (url, options) => {
196+
options = options ?? {};
197+
options.headers = {
198+
...options.headers,
199+
'x-my-custom-header': 'hello world',
200+
};
201+
return fetch(url, options);
202+
};
203+
204+
export const appConfig: ApplicationConfig = {
205+
providers: [
206+
provideTanStackQuery(new QueryClient()),
207+
provideAngularQueryContext({
208+
endpoint: 'http://localhost:3000/v1/api/rpc',
209+
fetch: myFetch,
210+
logging: true,
211+
}),
212+
],
213+
};
214+
215+
```
216+
217+
</TabItem>
218+
184219
</Tabs>
185220
186221
:::info Notes about Next.js app router
@@ -339,6 +374,44 @@ export default config;
339374
340375
</TabItem>
341376
377+
378+
<TabItem value="angular" label="Angular">
379+
380+
```typescript title='src/app/posts/posts.component.ts'
381+
382+
import {
383+
ApplicationConfig,
384+
provideBrowserGlobalErrorListeners,
385+
provideZonelessChangeDetection,
386+
} from '@angular/core';
387+
import { provideRouter, withComponentInputBinding } from '@angular/router';
388+
import { routes } from './app.routes';
389+
import { provideAngularQueryContext } from '@/lib/hooks';
390+
import {
391+
provideTanStackQuery,
392+
QueryClient,
393+
withDevtools,
394+
} from '@tanstack/angular-query-experimental';
395+
396+
397+
export const appConfig: ApplicationConfig = {
398+
providers: [
399+
provideBrowserGlobalErrorListeners(),
400+
provideZonelessChangeDetection(),
401+
provideTanStackQuery(new QueryClient(), withDevtools()),
402+
provideAngularQueryContext({
403+
endpoint: 'http://localhost:3000/v1/api/rpc',
404+
}),
405+
provideRouter(routes, withComponentInputBinding()),
406+
],
407+
};
408+
409+
410+
```
411+
412+
</TabItem>
413+
414+
342415
</Tabs>
343416
344417
#### Using Query and Mutation Hooks
@@ -473,6 +546,91 @@ const { data: posts } = useFindManyPost(queryParams);
473546
</div>
474547
```
475548
549+
</TabItem>
550+
551+
<TabItem value="angular" label="Angular">
552+
553+
```typescript title='src/app/posts/posts.component.ts'
554+
import { CommonModule } from '@angular/common';
555+
import {
556+
ChangeDetectionStrategy,
557+
Component,
558+
computed,
559+
input,
560+
signal,
561+
} from '@angular/core';
562+
import { useCreatePost, useFindManyPost } from '@lib/hooks/generatedAPI';
563+
564+
@Component({
565+
selector: 'app-posts-component',
566+
standalone: true,
567+
imports: [CommonModule],
568+
template: ` <div>
569+
<button (click)="onCreatePost()">Create</button>
570+
@if (posts.data()) {
571+
<ul>
572+
@for (post of posts.data(); track post.id) {
573+
<li>{{ post.title }} by {{ post.author.email }}</li>
574+
}
575+
</ul>
576+
}
577+
</div>
578+
<div>
579+
<p>Filtered Posts</p>
580+
<input
581+
#searchInput
582+
type="text"
583+
placeholder="Filter by title…"
584+
[value]="search()"
585+
(input)="search.set(searchInput.value)"
586+
aria-label="Filter posts by title"
587+
/>
588+
@if (filteredPosts.data()) {
589+
<ul>
590+
@for (post of filteredPosts.data(); track post.id) {
591+
<li>{{ post.title }} by {{ post.author.email }}</li>
592+
}
593+
</ul>
594+
}
595+
</div>`,
596+
changeDetection: ChangeDetectionStrategy.OnPush,
597+
})
598+
export class PostsComponent {
599+
id = input.required<string>();
600+
search = signal('');
601+
602+
posts = useFindManyPost({
603+
include: { author: true },
604+
orderBy: { createdAt: 'desc' },
605+
});
606+
607+
filteredPostsArgs = computed(() => {
608+
const search = this.search();
609+
610+
return {
611+
where: { title: { contains: search } },
612+
include: { author: true },
613+
orderBy: { createdAt: 'desc' } as const,
614+
}
615+
});
616+
617+
//For Reactivity in angular we have to pass the signal as callback
618+
filteredPosts = useFindManyPost(() => this.filteredPostsArgs());
619+
620+
create = useCreatePost();
621+
622+
onCreatePost() {
623+
this.create.mutate({
624+
data: {
625+
title: 'My awesome post',
626+
authorId: this.id(),
627+
},
628+
});
629+
}
630+
}
631+
632+
```
633+
476634
</TabItem>
477635
</Tabs>
478636
@@ -679,6 +837,73 @@ Here's a quick example of using infinite query to load a list of posts with infi
679837
</div>
680838
```
681839
840+
</TabItem>
841+
842+
<TabItem value="angular" label="Angular">
843+
844+
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.
845+
846+
```ts title='src/app/posts/posts.component.ts'
847+
import { CommonModule } from '@angular/common';
848+
import {
849+
ChangeDetectionStrategy,
850+
Component,
851+
computed,
852+
signal,
853+
} from '@angular/core';
854+
import { useInfiniteFindManyPost } from '@/lib/hooks';
855+
856+
@Component({
857+
selector: 'app-posts-component',
858+
standalone: true,
859+
imports: [CommonModule],
860+
template: `
861+
<div>
862+
@if (postsInfinite.data(); as data) {
863+
<ul>
864+
@for (posts of data.pages; track $index) {
865+
@for (post of posts; track post.id) {
866+
<li>{{ post.title }} by {{ post.author.email }}</li>
867+
}
868+
}
869+
</ul>
870+
}
871+
</div>
872+
873+
@if (postsInfinite.hasNextPage()) {
874+
<button (click)="postsInfinite.fetchNextPage()">Load More</button>
875+
}
876+
`,
877+
changeDetection: ChangeDetectionStrategy.OnPush,
878+
})
879+
export class PostsComponent {
880+
PAGE_SIZE = signal(10);
881+
882+
fetchArgs = computed(() => {
883+
return {
884+
include: { author: true },
885+
orderBy: { createdAt: 'desc' as const },
886+
take: this.PAGE_SIZE(),
887+
};
888+
});
889+
890+
postsInfinite = useInfiniteFindManyPost(() => this.fetchArgs(), {
891+
getNextPageParam: (lastPage, pages) => {
892+
if (lastPage.length < this.PAGE_SIZE()) {
893+
return undefined;
894+
}
895+
const fetched = pages.flatMap((item) => item).length;
896+
return {
897+
...this.fetchArgs(),
898+
skip: fetched,
899+
};
900+
},
901+
});
902+
}
903+
904+
905+
```
906+
682907
</TabItem>
683908
</Tabs>
684909

0 commit comments

Comments
 (0)