Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
d537867
docs(angular-query): add TypeScript documentation
arnoud-dv Nov 15, 2024
77390c4
Improve PendingTasks task cleanup, isRestoring() handling
arnoud-dv Nov 22, 2025
7ab845f
Ensure unit tests are run using component effect scheduling
arnoud-dv Nov 22, 2025
0989a98
Use tracking to fix some subtle bugs
arnoud-dv Nov 22, 2025
0426aa5
Fix PendingTasks for offline mode
arnoud-dv Nov 22, 2025
6de90c4
Use queueMicrotask instead of notifyManager.batchCalls to improve timing
arnoud-dv Nov 22, 2025
3b2d182
Fix isRestoring() handling
arnoud-dv Nov 23, 2025
da00953
add changeset
arnoud-dv Nov 23, 2025
33e0112
Improve tests
arnoud-dv Nov 23, 2025
db2335c
fix(angular-query): statically split proxy objects to avoid reading i…
benjavicente Dec 18, 2025
c0037d4
fix(angular-query): start pending task early and don't start on destr…
benjavicente Dec 20, 2025
9c95e03
tests(angular-query): add pending task ssr render test
benjavicente Dec 20, 2025
d607156
fix(angular-query): inject query types and tests
benjavicente Dec 20, 2025
ddd6d91
chore(angular-query): require angular 19 peer
benjavicente Dec 20, 2025
3e1c881
fix(angular-query): infer select instead of skip-token like other sig…
benjavicente Dec 21, 2025
c9dff80
docs(angular-query): consistency on examples and small improvements
benjavicente Dec 21, 2025
0e4a472
chore(angular-query): fix eslint and knip warnings
benjavicente Dec 21, 2025
20d930d
chore(angular-query): remove compat pending tasks for angular <19
benjavicente Dec 21, 2025
530cb6c
tests(angular-query): only test supported ts versions of angular
benjavicente Dec 21, 2025
a6178d7
tests(angular-query): assert other status narrowing on infinite queries
benjavicente Dec 21, 2025
570456a
docs(tanstack-query): other docs improvements
benjavicente Dec 21, 2025
ee08eb9
docs(angular-query): remove status type narrowing example
benjavicente Dec 21, 2025
40f9985
tests(angular-query): improve optimistic mutation test
benjavicente Dec 22, 2025
65e15b3
fix(angular-query): use signal observer in createBaseQuery
benjavicente Dec 22, 2025
9a4f419
docs(angular-query): fix typos
benjavicente Dec 27, 2025
9ed0697
feat(angular-query): inject queries refractor + tests
benjavicente Feb 8, 2026
0100a69
feat(angular-query): update docs and references
benjavicente Feb 8, 2026
a5b07cf
feat(angular-query): address coderabbit comments
benjavicente Feb 9, 2026
bbd2fab
feat(angular-query): untracked instead of lazy let in mutation observer
benjavicente Feb 21, 2026
1ce9da5
remove inject queries entrypoint
benjavicente Mar 22, 2026
412c305
remove deprecated provideAngularQuery
benjavicente Mar 22, 2026
14719bd
move devtools outside main angular package
benjavicente Mar 22, 2026
6e629c9
bump zone 16 in examples
benjavicente Mar 22, 2026
1ee25d4
use provideEnvironmentInitializer
benjavicente Mar 22, 2026
ac96a52
bump zonejs to 16
benjavicente Mar 22, 2026
199c70b
use in for detecting has in signal proxy
benjavicente Mar 22, 2026
8d005c7
null data in injectQueries example
benjavicente Mar 22, 2026
c08342e
inject queries and await callbacks on persister
benjavicente Mar 22, 2026
5994e77
remove deprecated injectQueryClient
benjavicente Mar 22, 2026
66fc7ee
zonejs stuff
benjavicente Mar 22, 2026
502a5e0
jsdocs cleanup
benjavicente Mar 22, 2026
3d2e61a
refresh generated docs
benjavicente Mar 22, 2026
5edfb20
version mismatch
benjavicente Mar 22, 2026
64b214e
fix devtools docs links
benjavicente Mar 22, 2026
e3e8528
add devtools to changeset
benjavicente Mar 22, 2026
08fc22b
fix import issues with tests
benjavicente Mar 22, 2026
a955b7d
fix lint issue in import of mutation options
benjavicente Mar 22, 2026
578023b
use analog plugin for compiling angular components in specs
benjavicente Mar 24, 2026
0091394
move ssr test to a single file
benjavicente Mar 24, 2026
ff84b07
hydratation angular library
benjavicente Mar 26, 2026
29cf953
remove extra package and update docs
benjavicente Mar 26, 2026
5c44ccc
sync lockfile
benjavicente Mar 26, 2026
4687cf0
dynamic devtools example
benjavicente Mar 26, 2026
2cdb850
persister ssr support
benjavicente Mar 27, 2026
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
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"fixed": [
[
"@tanstack/angular-query-experimental",
"@tanstack/angular-query-devtools",
"@tanstack/angular-query-persist-client",
"@tanstack/eslint-plugin-query",
"@tanstack/preact-query",
Expand Down
5 changes: 5 additions & 0 deletions .changeset/deep-crews-open.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/angular-query-experimental': minor
---

require Angular v19+ and use Angular component effect scheduling
8 changes: 8 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@
"label": "Devtools",
"to": "framework/angular/devtools"
},
{
"label": "SSR",
"to": "framework/angular/guides/ssr"
},
{
"label": "TypeScript",
"to": "framework/angular/typescript"
Expand Down Expand Up @@ -1542,6 +1546,10 @@
{
"label": "Devtools embedded panel",
"to": "framework/angular/examples/devtools-panel"
},
{
"label": "SSR",
"to": "framework/angular/examples/ssr"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Because TanStack Query's fetching mechanisms are agnostically built on Promises,
- Mock responses in unit tests using [provideHttpClientTesting](https://angular.dev/guide/http/testing).
- [Interceptors](https://angular.dev/guide/http/interceptors) can be used for a wide range of functionality including adding authentication headers, performing logging, etc. While some data fetching libraries have their own interceptor system, `HttpClient` interceptors are integrated with Angular's dependency injection system.
- `HttpClient` automatically informs [`PendingTasks`](https://angular.dev/api/core/PendingTasks#), which enables Angular to be aware of pending requests. Unit tests and SSR can use the resulting application _stableness_ information to wait for pending requests to finish. This makes unit testing much easier for [Zoneless](https://angular.dev/guide/zoneless) applications.
- When using SSR, `HttpClient` will [cache requests](https://angular.dev/guide/ssr#caching-data-when-using-HttpClient) performed on the server. This will prevent unneeded requests on the client. `HttpClient` SSR caching works out of the box. TanStack Query has its own hydration functionality which may be more powerful but requires some setup. Which one fits your needs best depends on your use case.
- When using SSR, `HttpClient` will [cache requests](https://angular.dev/guide/ssr#caching-data-when-using-HttpClient) performed on the server. This will prevent unneeded requests on the client, but will not avoid an initial loading state when the HttpClient has not been called yet on the client. You can use `withHydration` plus `provideServerTanStackQueryHydration` to hydratate the loading state of Tanstack Query. See the [SSR guide](./guides/ssr.md) for more information.

### Using observables in `queryFn`

Expand All @@ -36,8 +36,6 @@ class ExampleComponent {
```

> Since Angular is moving towards RxJS as an optional dependency, it's expected that `HttpClient` will also support promises in the future.
>
> Support for observables in TanStack Query for Angular is planned.

## Comparison table

Expand Down
30 changes: 18 additions & 12 deletions docs/framework/angular/devtools.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ title: Devtools

## Enable devtools

Add the devtools package (in addition to `@tanstack/angular-query-experimental`):

```bash
npm install @tanstack/angular-query-devtools
```

The devtools help you debug and inspect your queries and mutations. You can enable the devtools by adding `withDevtools` to `provideTanStackQuery`.

By default, Angular Query Devtools are only included in development mode bundles, so you don't need to worry about excluding them during a production build.
By default, Angular Query Devtools are only included in development, so you don't need to worry about excluding them in production.

```ts
import {
QueryClient,
provideTanStackQuery,
} from '@tanstack/angular-query-experimental'

import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { withDevtools } from '@tanstack/angular-query-devtools'

export const appConfig: ApplicationConfig = {
providers: [provideTanStackQuery(new QueryClient(), withDevtools())],
Expand All @@ -30,20 +36,18 @@ export const appConfig: ApplicationConfig = {

## Devtools in production

Devtools are automatically excluded from production builds. However, it might be desirable to lazy load the devtools in production.

To use `withDevtools` in production builds, import using the `production` sub-path. The function exported from the production subpath is identical to the main one, but won't be excluded from production builds.
If you need the real implementation in production, import from the `production` entrypoint.

```ts
import { withDevtools } from '@tanstack/angular-query-experimental/devtools/production'
import { withDevtools } from '@tanstack/angular-query-devtools/production'
```

To control when devtools are loaded, you can use the `loadDevtools` option.

When not setting the option or setting it to 'auto', the devtools will be loaded automatically only when Angular runs in development mode.
When not setting the option or setting it to `'auto'`, devtools will only be loaded in development mode.

```ts
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { withDevtools } from '@tanstack/angular-query-devtools'

provideTanStackQuery(new QueryClient(), withDevtools())

Expand All @@ -61,7 +65,7 @@ This is useful if you want to load devtools based on [Angular environment config
```ts
import { environment } from './environments/environment'
// Make sure to use the production sub-path to load devtools in production builds
import { withDevtools } from '@tanstack/angular-query-experimental/devtools/production'
import { withDevtools } from '@tanstack/angular-query-devtools/production'

provideTanStackQuery(
new QueryClient(),
Expand Down Expand Up @@ -107,14 +111,16 @@ export class DevtoolsOptionsManager {
}
```

See also the runnable example at `examples/angular/dynamic-devtools`.

If you want to use an injectable such as a service in the callback you can use `deps`. The injected value will be passed as parameter to the callback function.

This is similar to `deps` in Angular's [`useFactory`](https://angular.dev/guide/di/dependency-injection-providers#factory-providers-usefactory) provider.

```ts
// ...
// 👇 Note we import from the production sub-path to enable devtools lazy loading in production builds
import { withDevtools } from '@tanstack/angular-query-experimental/devtools/production'
import { withDevtools } from '@tanstack/angular-query-devtools/production'

export const appConfig: ApplicationConfig = {
providers: [
Expand All @@ -140,8 +146,8 @@ export const appConfig: ApplicationConfig = {
Of these options `loadDevtools`, `client`, `position`, `errorTypes`, `buttonPosition`, and `initialIsOpen` support reactivity through signals.

- `loadDevtools?: 'auto' | boolean`
- Defaults to `auto`: lazily loads devtools when in development mode. Skips loading in production mode.
- Use this to control if the devtools are loaded.
- Omit or `'auto'`: uses **`isDevMode()`** to decide whether to mount devtools (see above; only when the full implementation is bundled).
- Use this to control whether devtools load when using the `/production` import or in other setups where the stub is not used.
- `initialIsOpen?: Boolean`
- Set this to `true` if you want the tools to default to being open
- `buttonPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | "relative"`
Expand Down
6 changes: 3 additions & 3 deletions docs/framework/angular/guides/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ Let's assume we are using the default `gcTime` of **5 minutes** and the default
- When the request completes successfully, the cache's data under the `['todos']` key is updated with the new data, and both instances are updated with the new data.
- Both instances of the `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos }))` query are destroyed and no longer in use.
- Since there are no more active instances of this query, a garbage collection timeout is set using `gcTime` to delete and garbage collect the query (defaults to **5 minutes**).
- Before the cache timeout has completed, another instance of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos }))` mounts. The query immediately returns the available cached data while the `fetchTodos` function is being run in the background. When it completes successfully, it will populate the cache with fresh data.
- The final instance of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos }))` gets destroyed.
- No more instances of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos }))` appear within **5 minutes**.
- Before the cache timeout has completed, another instance of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos })` mounts. The query immediately returns the available cached data while the `fetchTodos` function is being run in the background. When it completes successfully, it will populate the cache with fresh data.
- The final instance of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos })` gets destroyed.
- No more instances of `injectQuery(() => ({ queryKey: ['todos'], queryFn: fetchTodos })` appear within **5 minutes**.
- The cached data under the `['todos']` key is deleted and garbage collected.

For more advanced use-cases, see [injectQuery](../reference/functions/injectQuery.md).
16 changes: 12 additions & 4 deletions docs/framework/angular/guides/default-query-function.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,27 @@ bootstrapApplication(MyAppComponent, {
providers: [provideTanStackQuery(queryClient)],
})

export class PostsComponent {
@Component({
// ...
})
class PostsComponent {
// All you have to do now is pass a key!
postsQuery = injectQuery<Array<Post>>(() => ({
queryKey: ['/posts'],
}))
// ...
}

export class PostComponent {
@Component({
// ...
})
class PostComponent {
postId = input(0)

// You can even leave out the queryFn and just go straight into options
postQuery = injectQuery<Post>(() => ({
enabled: this.postIdSignal() > 0,
queryKey: [`/posts/${this.postIdSignal()}`],
enabled: this.postId() > 0,
queryKey: [`/posts/${this.postId()}`],
}))
// ...
}
Expand Down
24 changes: 20 additions & 4 deletions docs/framework/angular/guides/dependent-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ replace: { 'useQuery': 'injectQuery', 'useQueries': 'injectQueries' }
```ts
// Get the user
userQuery = injectQuery(() => ({
queryKey: ['user', email],
queryFn: getUserByEmail,
queryKey: ['user', this.email()],
queryFn: this.getUserByEmail,
}))

// Then get the user's projects
projectsQuery = injectQuery(() => ({
queryKey: ['projects', this.userQuery.data()?.id],
queryFn: getProjectsByUser,
queryFn: this.getProjectsByUser,
// The query will not execute until the user id exists
enabled: !!this.userQuery.data()?.id,
}))
Expand All @@ -26,8 +26,24 @@ projectsQuery = injectQuery(() => ({
[//]: # 'Example'
[//]: # 'Example2'

Dynamic parallel query - `injectQueries` can depend on a previous query also, here's how to achieve this:

```ts
// injectQueries is under development for Angular Query
// Get the users ids
userIdsQuery = injectQuery(() => ({
queryKey: ['users'],
queryFn: getUsersData,
select: (users) => users.map((user) => user.id),
}))

// Then get the users' messages
usersMessages = injectQueries(() => ({
queries:
this.userIdsQuery.data()?.map((id) => ({
queryKey: ['messages', id],
queryFn: () => getMessagesByUsers(id),
})) ?? [],
}))
```

[//]: # 'Example2'
6 changes: 3 additions & 3 deletions docs/framework/angular/guides/disabling-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ replace: { 'useQuery': 'injectQuery' }
template: `<div>
<button (click)="query.refetch()">Fetch Todos</button>
@if (query.data()) {
@if (query.data(); as data) {
<ul>
@for (todo of query.data(); track todo.id) {
@for (todo of data; track todo.id) {
<li>{{ todo.title }}</li>
}
</ul>
Expand Down Expand Up @@ -70,7 +70,7 @@ export class TodosComponent {
[//]: # 'Example3'

```angular-ts
import { skipToken, injectQuery } from '@tanstack/query-angular'
import { skipToken, injectQuery } from '@tanstack/angular-query-experimental'
@Component({
selector: 'todos',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ replace:
'useQuery': 'injectQuery',
'useMutation': 'injectMutation',
'hook': 'function',
'Redux, MobX or': 'NgRx Store or',
'Redux, MobX, Zustand': 'NgRx Store, custom services with RxJS',
}
---
1 change: 0 additions & 1 deletion docs/framework/angular/guides/important-defaults.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ title: Important Defaults
ref: docs/framework/react/guides/important-defaults.md
replace:
{
'React': 'Angular',
'react-query': 'angular-query',
'useQuery': 'injectQuery',
'useInfiniteQuery': 'injectInfiniteQuery',
Expand Down
2 changes: 2 additions & 0 deletions docs/framework/angular/guides/infinite-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export class Example {
template: ` <list-component (endReached)="fetchNextPage()" /> `,
})
export class Example {
projectsService = inject(ProjectsService)

query = injectInfiniteQuery(() => ({
queryKey: ['projects'],
queryFn: async ({ pageParam }) => {
Expand Down
10 changes: 6 additions & 4 deletions docs/framework/angular/guides/initial-query-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ result = injectQuery(() => ({
```ts
result = injectQuery(() => ({
queryKey: ['todo', this.todoId()],
queryFn: () => fetch('/todos'),
queryFn: () => fetch(`/todos/${this.todoId()}`),
initialData: () => {
// Use a todo from the 'todos' query as the initial data for this todo query
return this.queryClient
Expand All @@ -99,9 +99,11 @@ result = injectQuery(() => ({
queryKey: ['todos', this.todoId()],
queryFn: () => fetch(`/todos/${this.todoId()}`),
initialData: () =>
queryClient.getQueryData(['todos'])?.find((d) => d.id === this.todoId()),
this.queryClient
.getQueryData(['todos'])
?.find((d) => d.id === this.todoId()),
initialDataUpdatedAt: () =>
queryClient.getQueryState(['todos'])?.dataUpdatedAt,
this.queryClient.getQueryState(['todos'])?.dataUpdatedAt,
}))
```

Expand All @@ -114,7 +116,7 @@ result = injectQuery(() => ({
queryFn: () => fetch(`/todos/${this.todoId()}`),
initialData: () => {
// Get the query state
const state = queryClient.getQueryState(['todos'])
const state = this.queryClient.getQueryState(['todos'])

// If the query exists and has data that is no older than 10 seconds...
if (state && Date.now() - state.dataUpdatedAt <= 10 * 1000) {
Expand Down
11 changes: 10 additions & 1 deletion docs/framework/angular/guides/invalidations-from-mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
id: invalidations-from-mutations
title: Invalidations from Mutations
ref: docs/framework/react/guides/invalidations-from-mutations.md
replace: { 'useMutation': 'injectMutation', 'hook': 'function' }
replace:
{
'react-query': 'angular-query-experimental',
'useMutation': 'injectMutation',
'hook': 'function',
}
---

[//]: # 'Example'
Expand All @@ -17,11 +22,15 @@ mutation = injectMutation(() => ({
[//]: # 'Example2'

```ts
import { Component, inject } from '@angular/core'
import {
injectMutation,
QueryClient,
} from '@tanstack/angular-query-experimental'

@Component({
// ...
})
export class TodosComponent {
queryClient = inject(QueryClient)

Expand Down
7 changes: 5 additions & 2 deletions docs/framework/angular/guides/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,15 @@ queryClient.setMutationDefaults(['addTodo'], {
retry: 3,
})

class someComponent {
@Component({
// ...
})
class SomeComponent {
// Start mutation in some component:
mutation = injectMutation(() => ({ mutationKey: ['addTodo'] }))

someMethod() {
mutation.mutate({ title: 'title' })
this.mutation.mutate({ title: 'title' })
}
}

Expand Down
6 changes: 3 additions & 3 deletions docs/framework/angular/guides/paginated-queries.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ const result = injectQuery(() => ({
instantaneously while they are also re-fetched invisibly in the
background.
</p>
@if (query.status() === 'pending') {
@if (query.isPending()) {
<div>Loading...</div>
} @else if (query.status() === 'error') {
} @else if (query.isError()) {
<div>Error: {{ query.error().message }}</div>
} @else {
<!-- 'data' will either resolve to the latest page's data -->
Expand Down Expand Up @@ -83,7 +83,7 @@ export class PaginationExampleComponent {
effect(() => {
// Prefetch the next page!
if (!this.query.isPlaceholderData() && this.query.data()?.hasMore) {
this.#queryClient.prefetchQuery({
this.queryClient.prefetchQuery({
queryKey: ['projects', this.page() + 1],
queryFn: () => lastValueFrom(fetchProjects(this.page() + 1)),
})
Expand Down
Loading