Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 227 additions & 2 deletions docs/reference/plugins/tanstack-query.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -181,6 +181,40 @@ provideHooksContext({
```
</TabItem>

<TabItem value="angular" label="Angular">

```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,
}),
],
};

```

</TabItem>

</Tabs>

:::info Notes about Next.js app router
Expand Down Expand Up @@ -339,6 +373,44 @@ export default config;

</TabItem>


<TabItem value="angular" label="Angular">

```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()),
],
};


```

</TabItem>


</Tabs>

#### Using Query and Mutation Hooks
Expand Down Expand Up @@ -473,6 +545,92 @@ const { data: posts } = useFindManyPost(queryParams);
</div>
```

</TabItem>

<TabItem value="angular" label="Angular">

```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: ` <div>
<button (click)="onCreatePost()">Create</button>
@if (posts.data()) {
<ul>
@for (post of posts.data(); track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
</ul>
}
</div>
<div>
<p>Filtered Posts</p>
<input
#searchInput
type="text"
placeholder="Filter by title…"
[value]="search()"
(input)="search.set(searchInput.value)"
aria-label="Filter posts by title"
/>
@if (filteredPosts.data()) {
<ul>
@for (post of filteredPosts.data(); track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
</ul>
}
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PostsComponent {
id = input.required<string>();
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(),
},
});
}
}

```

</TabItem>
</Tabs>

Expand Down Expand Up @@ -679,6 +837,73 @@ Here's a quick example of using infinite query to load a list of posts with infi
</div>
```

</TabItem>

<TabItem value="angular" label="Angular">

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: `
<div>
@if (postsInfinite.data(); as data) {
<ul>
@for (posts of data.pages; track $index) {
@for (post of posts; track post.id) {
<li>{{ post.title }} by {{ post.author.email }}</li>
}
}
</ul>
}
</div>

@if (postsInfinite.hasNextPage()) {
<button (click)="postsInfinite.fetchNextPage()">Load More</button>
}
`,
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,
};
},
});
}


```

</TabItem>
</Tabs>

Expand Down