Skip to content

feat(angular-query): move devtools to conditional sub-paths #9270

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
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
2 changes: 0 additions & 2 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'package: angular-query-devtools-experimental':
- 'packages/angular-query-devtools-experimental/**/*'
'package: angular-query-experimental':
- 'packages/angular-query-experimental/**/*'
'package: eslint-plugin-query':
4 changes: 0 additions & 4 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -19,10 +19,6 @@ comment:

component_management:
individual_components:
- component_id: angular-query-devtools-experimental
name: '@tanstack/angular-query-devtools-experimental'
paths:
- packages/angular-query-devtools-experimental/**
- component_id: angular-query-experimental
name: '@tanstack/angular-query-experimental'
paths:
73 changes: 56 additions & 17 deletions docs/framework/angular/devtools.md
Original file line number Diff line number Diff line change
@@ -7,27 +7,38 @@ title: Devtools

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

By default, the devtools are enabled when Angular [`isDevMode`](https://angular.dev/api/core/isDevMode) returns true. So you don't need to worry about excluding them during a production build. The core tools are lazily loaded and excluded from bundled code. In most cases, all you'll need to do is add `withDevtools()` to `provideTanStackQuery` without any additional configuration.
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.

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

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

export const appConfig: ApplicationConfig = {
providers: [provideTanStackQuery(new QueryClient(), withDevtools())],
}
```

## Configuring if devtools are loaded
## 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.

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

If you need more control over when devtools are loaded, you can use the `loadDevtools` option. This is particularly useful if you want to load devtools based on environment configurations. For instance, you might have a test environment running in production mode but still require devtools to be available.
To control when devtools are loaded, you can use the `loadDevtools` option. This is particularly useful if you want to load devtools based on environment configurations or user interaction. For instance, you might have a test environment running in production mode but still require devtools to be available.

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

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

provideTanStackQuery(new QueryClient(), withDevtools())

// which is equivalent to
@@ -39,7 +50,12 @@ provideTanStackQuery(

When setting the option to true, the devtools will be loaded in both development and production mode.

This is particularly useful if you want to load devtools based on environment configurations. E.g. you could set this to true either when `isDevMode()` is true or when the application is running on your production build staging environment.

```ts
// Make sure to use the production sub-path to load devtools in production builds
import { withDevtools } from '@tanstack/angular-query-experimental/devtools/production'

provideTanStackQuery(
new QueryClient(),
withDevtools(() => ({ loadDevtools: true })),
@@ -55,44 +71,67 @@ provideTanStackQuery(
)
```

The `withDevtools` options are returned from a callback function to support reactivity through signals. In the following example
a signal is created from a RxJS observable that listens for a keyboard shortcut. When the event is triggered, the devtools are lazily loaded.
Using this technique allows you to support on-demand loading of the devtools even in production mode, without including the full tools in the bundled code.
## Derive options through reactivity

Options are passed to `withDevtools` from a callback function to support reactivity through signals. In the following example
a signal is created from a RxJS observable that emits on a keyboard shortcut. When the derived signal is set to true, the devtools are lazily loaded.

> If you don't need devtools in production builds, don't use the `production` sub-path. Even though most of the devtools are lazy loaded on-demand, code is needed for on-demand loading and option handling. When importing devtools from `@tanstack/angular-query-experimental/devtools`, all devtools code will be excluded from your build and no lazy chunks will be created, minimizing deployment size.

The example below always loads devtools in development mode and loads on-demand in production mode when a keyboard shortcut is pressed.

```ts
import { Injectable, isDevMode } from '@angular/core'
import { fromEvent, map, scan } from 'rxjs'
import { toSignal } from '@angular/core/rxjs-interop'

@Injectable({ providedIn: 'root' })
class DevtoolsOptionsManager {
export class DevtoolsOptionsManager {
loadDevtools = toSignal(
fromEvent<KeyboardEvent>(document, 'keydown').pipe(
map(
(event): boolean =>
event.metaKey && event.ctrlKey && event.shiftKey && event.key === 'D',
),
scan((acc, curr) => acc || curr, false),
scan((acc, curr) => acc || curr, isDevMode()),
),
{
initialValue: false,
initialValue: isDevMode(),
},
)
}
```

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
// ...
import { withDevtools } from '@tanstack/angular-query-experimental/devtools/production'

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
provideTanStackQuery(
new QueryClient(),
withDevtools(() => ({
initialIsOpen: true,
loadDevtools: inject(DevtoolsOptionsManager).loadDevtools(),
})),
withDevtools(
(devToolsOptionsManager: DevtoolsOptionsManager) => ({
loadDevtools: devToolsOptionsManager.loadDevtools(),
}),
{
// `deps` is used to inject and pass `DevtoolsOptionsManager` to the `withDevtools` callback.
deps: [DevtoolsOptionsManager],
},
),
),
],
}
```

### Options
### Options returned from the callback

Of these options `client`, `position`, `errorTypes`, `buttonPosition`, and `initialIsOpen` support reactivity through signals.
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.
4 changes: 2 additions & 2 deletions docs/framework/angular/reference/functions/withdevtools.md
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ title: withDevtools
# Function: withDevtools()

```ts
function withDevtools(withDevtoolsFn?): DeveloperToolsFeature
function withDevtools(withDevtoolsFn?): DevtoolsFeature
```

Enables developer tools.
@@ -35,7 +35,7 @@ A function that returns `DevtoolsOptions`.

## Returns

[`DeveloperToolsFeature`](../../type-aliases/developertoolsfeature.md)
[`DevtoolsFeature`](../../type-aliases/developertoolsfeature.md)

A set of providers for use with `provideTanStackQuery`.

2 changes: 1 addition & 1 deletion docs/framework/angular/reference/index.md
Original file line number Diff line number Diff line change
@@ -35,7 +35,7 @@ title: '@tanstack/angular-query-experimental'
- [DefinedCreateQueryResult](../type-aliases/definedcreatequeryresult.md)
- [DefinedInitialDataInfiniteOptions](../type-aliases/definedinitialdatainfiniteoptions.md)
- [DefinedInitialDataOptions](../type-aliases/definedinitialdataoptions.md)
- [DeveloperToolsFeature](../type-aliases/developertoolsfeature.md)
- [DevtoolsFeature](../type-aliases/developertoolsfeature.md)
- [PersistQueryClientFeature](../type-aliases/persistqueryclientfeature.md)
- [QueriesOptions](../type-aliases/queriesoptions.md)
- [QueriesResults](../type-aliases/queriesresults.md)
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
id: DeveloperToolsFeature
title: DeveloperToolsFeature
id: DevtoolsFeature
title: DevtoolsFeature
---

# Type Alias: DeveloperToolsFeature
# Type Alias: DevtoolsFeature

```ts
type DeveloperToolsFeature = QueryFeature<'DeveloperTools'>
type DevtoolsFeature = QueryFeature<'DeveloperTools'>
```

A type alias that represents a feature which enables developer tools.
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ title: QueryFeatures
# Type Alias: QueryFeatures

```ts
type QueryFeatures = DeveloperToolsFeature | PersistQueryClientFeature
type QueryFeatures = DevtoolsFeature | PersistQueryClientFeature
```

A type alias that represents all Query features available for use with `provideTanStackQuery`.
2 changes: 1 addition & 1 deletion examples/angular/auto-refetching/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import {
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { mockInterceptor } from './interceptor/mock-api.interceptor'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/basic-persister/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -2,10 +2,10 @@ import { provideHttpClient, withFetch } from '@angular/common/http'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withPersistQueryClient } from '@tanstack/angular-query-persist-client'
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import type { ApplicationConfig } from '@angular/core'

const localStoragePersister = createSyncStoragePersister({
2 changes: 1 addition & 1 deletion examples/angular/basic/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ import { provideHttpClient, withFetch } from '@angular/common/http'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import type { ApplicationConfig } from '@angular/core'

export const appConfig: ApplicationConfig = {
1 change: 0 additions & 1 deletion examples/angular/devtools-panel/package.json
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@
"@angular/platform-browser": "^20.0.0",
"@angular/platform-browser-dynamic": "^20.0.0",
"@angular/router": "^20.0.0",
"@tanstack/angular-query-devtools-experimental": "^5.80.8",
"@tanstack/angular-query-experimental": "^5.80.8",
"rxjs": "^7.8.2",
"tslib": "^2.8.1",
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ import {
signal,
viewChild,
} from '@angular/core'
import { injectDevtoolsPanel } from '@tanstack/angular-query-devtools-experimental'
import { injectDevtoolsPanel } from '@tanstack/angular-query-experimental/devtools-panel'
import { ExampleQueryComponent } from './example-query.component'
import type { ElementRef } from '@angular/core'

Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ import {
} from '@angular/core'
import { ExampleQueryComponent } from './example-query.component'
import type { ElementRef } from '@angular/core'
import type { DevtoolsPanelRef } from '@tanstack/angular-query-devtools-experimental'
import type { DevtoolsPanelRef } from '@tanstack/angular-query-experimental/devtools-panel'

@Component({
selector: 'lazy-load-devtools-panel-example',
@@ -49,7 +49,7 @@ export default class LazyLoadDevtoolsPanelExampleComponent {
if (this.devtools()) return
if (this.isOpen()) {
this.devtools.set(
import('@tanstack/angular-query-devtools-experimental').then(
import('@tanstack/angular-query-experimental/devtools-panel').then(
({ injectDevtoolsPanel }) =>
injectDevtoolsPanel(this.devToolsOptions, {
injector: this.injector,
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import {
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { projectsMockInterceptor } from './api/projects-mock.interceptor'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/optimistic-updates/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import {
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { mockInterceptor } from './interceptor/mock-api.interceptor'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/pagination/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import {
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { projectsMockInterceptor } from './api/projects-mock.interceptor'
import type { ApplicationConfig } from '@angular/core'

Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@ import { provideRouter, withComponentInputBinding } from '@angular/router'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'

import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { routes } from './app.routes'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/router/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@ import { provideRouter, withComponentInputBinding } from '@angular/router'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'

import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { routes } from './app.routes'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/rxjs/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -6,8 +6,8 @@ import {
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import { autocompleteMockInterceptor } from './api/autocomplete-mock.interceptor'
import type { ApplicationConfig } from '@angular/core'

2 changes: 1 addition & 1 deletion examples/angular/simple/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -2,8 +2,8 @@ import { provideHttpClient, withFetch } from '@angular/common/http'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { withDevtools } from '@tanstack/angular-query-experimental/devtools'
import type { ApplicationConfig } from '@angular/core'

export const appConfig: ApplicationConfig = {
3 changes: 3 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -14,6 +14,9 @@
],
"ignoreWorkspaces": ["examples/**", "integrations/**"],
"workspaces": {
"packages/angular-query-experimental": {
"ignore": ["**/production/index.ts"]
},
"packages/query-codemods": {
"entry": ["src/v4/**/*.cjs", "src/v5/**/*.cjs"],
"ignore": ["**/__testfixtures__/**"]
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -79,7 +79,6 @@
},
"pnpm": {
"overrides": {
"@tanstack/angular-query-devtools-experimental": "workspace:*",
"@tanstack/angular-query-experimental": "workspace:*",
"@tanstack/eslint-plugin-query": "workspace:*",
"@tanstack/query-async-storage-persister": "workspace:*",
7 changes: 0 additions & 7 deletions packages/angular-query-devtools-experimental/.attw.json

This file was deleted.

17 changes: 0 additions & 17 deletions packages/angular-query-devtools-experimental/eslint.config.js

This file was deleted.

68 changes: 0 additions & 68 deletions packages/angular-query-devtools-experimental/package.json

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion packages/angular-query-devtools-experimental/src/index.ts

This file was deleted.

10 changes: 0 additions & 10 deletions packages/angular-query-devtools-experimental/src/test-setup.ts

This file was deleted.

17 changes: 0 additions & 17 deletions packages/angular-query-devtools-experimental/tsconfig.json

This file was deleted.

This file was deleted.

13 changes: 0 additions & 13 deletions packages/angular-query-devtools-experimental/tsup.config.js

This file was deleted.

28 changes: 0 additions & 28 deletions packages/angular-query-devtools-experimental/vite.config.ts

This file was deleted.

8 changes: 1 addition & 7 deletions packages/angular-query-experimental/eslint.config.js
Original file line number Diff line number Diff line change
@@ -20,13 +20,7 @@ export default [
'jsdoc/require-hyphen-before-param-description': 1,
'jsdoc/sort-tags': 1,
'jsdoc/require-throws': 1,
'jsdoc/check-tag-names': [
'warn',
{
// Not compatible with Api Extractor @public
typed: false,
},
],
'jsdoc/check-tag-names': ['warn'],
},
},
{
42 changes: 39 additions & 3 deletions packages/angular-query-experimental/package.json
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@
"tanstack"
],
"scripts": {
"clean": "premove ./build ./coverage ./dist-ts ./**.d.ts",
"clean": "premove ./dist ./coverage ./dist-ts ./**.d.ts",
"compile": "tsc --build",
"test:eslint": "eslint ./src",
"test:types": "npm-run-all --serial test:types:*",
@@ -37,8 +37,8 @@
"test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build",
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build",
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build",
"test:types:tscurrent": "tsc --build",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
@@ -56,6 +56,24 @@
"@tanstack/custom-condition": "./src/index.ts",
"default": "./dist/index.mjs"
},
"./devtools": {
"types": "./dist/types/devtools/index.d.ts",
"development": "./dist/devtools/index.mjs",
"default": "./dist/devtools/stub.mjs"
},
"./devtools/production": {
"types": "./dist/types/devtools/production/index.d.ts",
"default": "./dist/devtools/index.mjs"
},
"./devtools-panel": {
"types": "./dist/types/devtools-panel/index.d.ts",
"development": "./dist/devtools-panel/index.mjs",
"default": "./dist/devtools-panel/stub.mjs"
},
"./devtools-panel/production": {
"types": "./dist/types/devtools-panel/production/index.d.ts",
"default": "./dist/devtools-panel/index.mjs"
},
"./package.json": {
"default": "./package.json"
}
@@ -94,6 +112,24 @@
"types": "./index.d.ts",
"default": "./dist/index.mjs"
},
"./devtools": {
"types": "./devtools/index.d.ts",
"development": "./dist/devtools/index.mjs",
"default": "./dist/devtools/stub.mjs"
},
"./devtools/production": {
"types": "./devtools/production/index.d.ts",
"default": "./dist/devtools/index.mjs"
},
"./devtools-panel": {
"types": "./devtools-panel/index.d.ts",
"development": "./dist/devtools-panel/index.mjs",
"default": "./dist/devtools-panel/stub.mjs"
},
"./devtools-panel/production": {
"types": "./devtools-panel/production/index.d.ts",
"default": "./dist/devtools-panel/index.mjs"
},
"./package.json": {
"default": "./package.json"
}
Original file line number Diff line number Diff line change
@@ -4,12 +4,10 @@ import {
signal,
} from '@angular/core'
import { TestBed } from '@angular/core/testing'
import {
QueryClient,
provideTanStackQuery,
} from '@tanstack/angular-query-experimental'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { injectDevtoolsPanel } from '../inject-devtools-panel'
import { QueryClient } from '@tanstack/query-core'
import { provideTanStackQuery } from '../providers'
import { injectDevtoolsPanel } from '../devtools-panel'

const mockDevtoolsPanelInstance = {
mount: vi.fn(),
150 changes: 119 additions & 31 deletions packages/angular-query-experimental/src/__tests__/providers.test.ts
Original file line number Diff line number Diff line change
@@ -3,22 +3,21 @@ import { QueryClient } from '@tanstack/query-core'
import { TestBed } from '@angular/core/testing'
import {
ENVIRONMENT_INITIALIZER,
EnvironmentInjector,
PLATFORM_ID,
createEnvironmentInjector,
isDevMode,
provideZonelessChangeDetection,
signal,
} from '@angular/core'
import { isDevMode } from '../util/is-dev-mode/is-dev-mode'
import { provideTanStackQuery, withDevtools } from '../providers'
import type { DevtoolsOptions } from '../providers'
import type { Mock } from 'vitest'
import { provideTanStackQuery } from '../providers'
import { withDevtools } from '../devtools'
import type {
DevtoolsButtonPosition,
DevtoolsErrorType,
DevtoolsPosition,
} from '@tanstack/query-devtools'

vi.mock('../util/is-dev-mode/is-dev-mode', () => ({
isDevMode: vi.fn(),
}))
import type { DevtoolsOptions } from '../devtools'

const mockDevtoolsInstance = {
mount: vi.fn(),
@@ -36,12 +35,20 @@ vi.mock('@tanstack/query-devtools', () => ({
TanstackQueryDevtools: mockTanstackQueryDevtools,
}))

describe('withDevtools feature', () => {
let isDevModeMock: Mock
vi.mock('@angular/core', async () => {
const actual = await vi.importActual('@angular/core')
return {
...actual,
isDevMode: vi.fn(),
}
})

const mockIsDevMode = vi.mocked(isDevMode)

describe('withDevtools feature', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
isDevModeMock = isDevMode as Mock
})

afterEach(() => {
@@ -50,61 +57,59 @@ describe('withDevtools feature', () => {

test.each([
{
description:
'should provide developer tools in development mode by default',
isDevModeValue: true,
description: 'should load developer tools in development mode',
isDevMode: true,
expectedCalled: true,
},
{
description:
'should not provide developer tools in production mode by default',
isDevModeValue: false,
description: 'should not load developer tools in production mode',
isDevMode: false,
expectedCalled: false,
},
{
description: `should provide developer tools in development mode when 'loadDeveloperTools' is set to 'auto'`,
isDevModeValue: true,
description: `should load developer tools in development mode when 'loadDevtools' is set to 'auto'`,
isDevMode: true,
loadDevtools: 'auto',
expectedCalled: true,
},
{
description: `should not provide developer tools in production mode when 'loadDeveloperTools' is set to 'auto'`,
isDevModeValue: false,
description: `should not load developer tools in production mode when 'loadDevtools' is set to 'auto'`,
isDevMode: false,
loadDevtools: 'auto',
expectedCalled: false,
},
{
description:
"should provide developer tools in development mode when 'loadDevtools' is set to true",
isDevModeValue: true,
"should load developer tools in development mode when 'loadDevtools' is set to true",
isDevMode: true,
loadDevtools: true,
expectedCalled: true,
},
{
description:
"should provide developer tools in production mode when 'loadDevtools' is set to true",
isDevModeValue: false,
"should load developer tools in production mode when 'loadDevtools' is set to true",
isDevMode: false,
loadDevtools: true,
expectedCalled: true,
},
{
description:
"should not provide developer tools in development mode when 'loadDevtools' is set to false",
isDevModeValue: true,
"should not load developer tools in development mode when 'loadDevtools' is set to false",
isDevMode: true,
loadDevtools: false,
expectedCalled: false,
},
{
description:
"should not provide developer tools in production mode when 'loadDevtools' is set to false",
isDevModeValue: false,
"should not load developer tools in production mode when 'loadDevtools' is set to false",
isDevMode: false,
loadDevtools: false,
expectedCalled: false,
},
])(
'$description',
async ({ isDevModeValue, loadDevtools, expectedCalled }) => {
isDevModeMock.mockReturnValue(isDevModeValue)
async ({ isDevMode: isDevModeValue, loadDevtools, expectedCalled }) => {
mockIsDevMode.mockReturnValue(isDevModeValue)

const providers = [
provideZonelessChangeDetection(),
@@ -127,6 +132,10 @@ describe('withDevtools feature', () => {

TestBed.inject(ENVIRONMENT_INITIALIZER)
await vi.runAllTimersAsync()
TestBed.tick()
await vi.dynamicImportSettled()
TestBed.tick()
await vi.dynamicImportSettled()

if (expectedCalled) {
expect(mockTanstackQueryDevtools).toHaveBeenCalled()
@@ -136,6 +145,85 @@ describe('withDevtools feature', () => {
},
)

it('should not continue loading devtools after injector is destroyed', async () => {
TestBed.configureTestingModule({
providers: [
provideZonelessChangeDetection(),
provideTanStackQuery(
new QueryClient(),
withDevtools(() => ({
loadDevtools: true,
})),
),
],
})

TestBed.inject(ENVIRONMENT_INITIALIZER)
// Destroys injector
TestBed.resetTestingModule()
await vi.runAllTimersAsync()

expect(mockTanstackQueryDevtools).not.toHaveBeenCalled()
})

it('should not create devtools again when already provided', async () => {
TestBed.configureTestingModule({
providers: [
provideZonelessChangeDetection(),
provideTanStackQuery(
new QueryClient(),
withDevtools(() => ({
loadDevtools: true,
})),
),
],
})

TestBed.inject(ENVIRONMENT_INITIALIZER)
await vi.runAllTimersAsync()

expect(mockTanstackQueryDevtools).toHaveBeenCalledTimes(1)

const injector = TestBed.inject(EnvironmentInjector)

createEnvironmentInjector(
[
withDevtools(() => ({
loadDevtools: true,
})).ɵproviders,
],
injector,
)

TestBed.inject(ENVIRONMENT_INITIALIZER)
await vi.runAllTimersAsync()

expect(mockTanstackQueryDevtools).toHaveBeenCalledTimes(1)
})

it('should not load devtools if platform is not browser', async () => {
TestBed.configureTestingModule({
providers: [
{
provide: PLATFORM_ID,
useValue: 'server',
},
provideZonelessChangeDetection(),
provideTanStackQuery(
new QueryClient(),
withDevtools(() => ({
loadDevtools: true,
})),
),
],
})

TestBed.inject(ENVIRONMENT_INITIALIZER)
await vi.runAllTimersAsync()

expect(mockTanstackQueryDevtools).not.toHaveBeenCalled()
})

it('should update error types', async () => {
const errorTypes = signal([] as Array<DevtoolsErrorType>)

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type {
InjectDevtoolsPanel,
DevtoolsPanelOptions,
InjectDevtoolsPanelOptions,
DevtoolsPanelRef,
} from './types'

export { injectDevtoolsPanel } from './inject-devtools-panel'
Original file line number Diff line number Diff line change
@@ -10,22 +10,13 @@ import {
untracked,
} from '@angular/core'
import { TanstackQueryDevtoolsPanel } from '@tanstack/query-devtools'
import {
QueryClient,
onlineManager,
} from '@tanstack/angular-query-experimental'
import { QueryClient, onlineManager } from '@tanstack/query-core'
import { isPlatformBrowser } from '@angular/common'
import type { ElementRef } from '@angular/core'
import type { DevtoolsErrorType } from '@tanstack/query-devtools'

export interface InjectDevtoolsPanelOptions {
/**
* The `Injector` in which to create the devtools panel.
*
* If this is not provided, the current injection context will be used instead (via `inject`).
*/
injector?: Injector
}
import type {
DevtoolsPanelOptions,
InjectDevtoolsPanel,
InjectDevtoolsPanelOptions,
} from './types'

/**
* Inject a TanStack Query devtools panel and render it in the DOM.
@@ -39,10 +30,10 @@ export interface InjectDevtoolsPanelOptions {
* @returns DevtoolsPanelRef
* @see https://tanstack.com/query/v5/docs/framework/angular/devtools
*/
export function injectDevtoolsPanel(
export const injectDevtoolsPanel: InjectDevtoolsPanel = (
injectDevtoolsPanelFn: () => DevtoolsPanelOptions,
options?: InjectDevtoolsPanelOptions,
): DevtoolsPanelRef {
) => {
!options?.injector && assertInInjectionContext(injectDevtoolsPanel)
const currentInjector = options?.injector ?? inject(Injector)

@@ -108,43 +99,3 @@ export function injectDevtoolsPanel(
}
})
}

/**
* A devtools panel, which can be manually destroyed.
*/
export interface DevtoolsPanelRef {
/**
* Destroy the panel, removing it from the DOM and stops listening to signal changes.
*/
destroy: () => void
}

export interface DevtoolsPanelOptions {
/**
* Custom instance of QueryClient
*/
client?: QueryClient
/**
* Use this so you can define custom errors that can be shown in the devtools.
*/
errorTypes?: Array<DevtoolsErrorType>
/**
* Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
*/
styleNonce?: string
/**
* Use this so you can attach the devtool's styles to specific element in the DOM.
*/
shadowDOMTarget?: ShadowRoot

/**
* Callback function that is called when the devtools panel is closed
*/
onClose?: () => unknown

/**
* Element where to render the devtools panel. When set to undefined or null, the devtools panel will not be created, or destroyed if existing.
* If changed from undefined to a ElementRef, the devtools panel will be created.
*/
hostElement?: ElementRef
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '..'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { noop } from '@tanstack/query-core'
import type { InjectDevtoolsPanel } from './types'

// Stub which replaces `injectDevtoolsPanel` in production builds
export const injectDevtoolsPanel: InjectDevtoolsPanel = () => ({
destroy: noop,
})
57 changes: 57 additions & 0 deletions packages/angular-query-experimental/src/devtools-panel/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { DevtoolsErrorType } from '@tanstack/query-devtools'
import type { ElementRef, Injector } from '@angular/core'
import type { QueryClient } from '@tanstack/query-core'

export interface InjectDevtoolsPanelOptions {
/**
* The `Injector` in which to create the devtools panel.
*
* If this is not provided, the current injection context will be used instead (via `inject`).
*/
injector?: Injector
}

/**
* A devtools panel, which can be manually destroyed.
*/
export interface DevtoolsPanelRef {
/**
* Destroy the panel, removing it from the DOM and stops listening to signal changes.
*/
destroy: () => void
}

export interface DevtoolsPanelOptions {
/**
* Custom instance of QueryClient
*/
client?: QueryClient
/**
* Use this so you can define custom errors that can be shown in the devtools.
*/
errorTypes?: Array<DevtoolsErrorType>
/**
* Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
*/
styleNonce?: string
/**
* Use this so you can attach the devtool's styles to specific element in the DOM.
*/
shadowDOMTarget?: ShadowRoot

/**
* Callback function that is called when the devtools panel is closed
*/
onClose?: () => unknown

/**
* Element where to render the devtools panel. When set to undefined or null, the devtools panel will not be created, or destroyed if existing.
* If changed from undefined to a ElementRef, the devtools panel will be created.
*/
hostElement?: ElementRef
}

export type InjectDevtoolsPanel = (
injectDevtoolsPanelFn: () => DevtoolsPanelOptions,
options?: InjectDevtoolsPanelOptions,
) => DevtoolsPanelRef
8 changes: 8 additions & 0 deletions packages/angular-query-experimental/src/devtools/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type {
DevtoolsOptions,
WithDevtools,
WithDevtoolsFn,
WithDevtoolsOptions,
} from './types'

export { withDevtools } from './with-devtools'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from '..'
7 changes: 7 additions & 0 deletions packages/angular-query-experimental/src/devtools/stub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { WithDevtools } from './types'

// Stub which replaces `withDevtools` in production builds
export const withDevtools: WithDevtools = () => ({
ɵkind: 'Devtools',
ɵproviders: [],
})
103 changes: 103 additions & 0 deletions packages/angular-query-experimental/src/devtools/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import type { QueryClient } from '@tanstack/query-core'
import type {
DevtoolsButtonPosition,
DevtoolsErrorType,
DevtoolsPosition,
} from '@tanstack/query-devtools'
import type { DevtoolsFeature } from '../providers'

/**
* Options for configuring withDevtools.
*/
export interface WithDevtoolsOptions {
/**
* An array of dependencies to be injected and passed to the `withDevtoolsFn` function.
*
* **Example**
* ```ts
* export const appConfig: ApplicationConfig = {
* providers: [
* provideTanStackQuery(
* new QueryClient(),
* withDevtools(
* (devToolsOptionsManager: DevtoolsOptionsManager) => ({
* loadDevtools: devToolsOptionsManager.loadDevtools(),
* }),
* {
* deps: [DevtoolsOptionsManager],
* },
* ),
* ),
* ],
* }
* ```
*/
deps?: Array<any>
}

/**
* Options for configuring the TanStack Query devtools.
*/
export interface DevtoolsOptions {
/**
* Set this true if you want the devtools to default to being open
*/
initialIsOpen?: boolean
/**
* The position of the TanStack logo to open and close the devtools panel.
* `top-left` | `top-right` | `bottom-left` | `bottom-right` | `relative`
* Defaults to `bottom-right`.
*/
buttonPosition?: DevtoolsButtonPosition
/**
* The position of the TanStack Query devtools panel.
* `top` | `bottom` | `left` | `right`
* Defaults to `bottom`.
*/
position?: DevtoolsPosition
/**
* Custom instance of QueryClient
*/
client?: QueryClient
/**
* Use this so you can define custom errors that can be shown in the devtools.
*/
errorTypes?: Array<DevtoolsErrorType>
/**
* Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
*/
styleNonce?: string
/**
* Use this so you can attach the devtool's styles to a specific element in the DOM.
*/
shadowDOMTarget?: ShadowRoot

/**
* Whether the developer tools should load.
* - `auto`- (Default) Lazily loads devtools when in development mode. Skips loading in production mode.
* - `true`- Always load the devtools, regardless of the environment.
* - `false`- Never load the devtools, regardless of the environment.
*
* You can use `true` and `false` to override loading developer tools from an environment file.
* For example, a test environment might run in production mode but you may want to load developer tools.
*
* Additionally, you can use a signal in the callback to dynamically load the devtools based on a condition. For example,
* a signal created from a RxJS observable that listens for a keyboard shortcut.
*
* **Example**
* ```ts
* withDevtools(() => ({
* initialIsOpen: true,
* loadDevtools: inject(ExampleService).loadDevtools()
* }))
* ```
*/
loadDevtools?: 'auto' | boolean
}

export type WithDevtoolsFn = (...deps: Array<any>) => DevtoolsOptions

export type WithDevtools = (
withDevtoolsFn?: WithDevtoolsFn,
options?: WithDevtoolsOptions,
) => DevtoolsFeature
173 changes: 173 additions & 0 deletions packages/angular-query-experimental/src/devtools/with-devtools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { isPlatformBrowser } from '@angular/common'
import {
DestroyRef,
ENVIRONMENT_INITIALIZER,
InjectionToken,
Injector,
PLATFORM_ID,
computed,
effect,
inject,
isDevMode,
} from '@angular/core'
import { QueryClient, noop, onlineManager } from '@tanstack/query-core'
import { queryFeature } from '../providers'
import type { Signal } from '@angular/core'
import type {
DevtoolsOptions,
WithDevtools,
WithDevtoolsFn,
WithDevtoolsOptions,
} from './types'
import type { TanstackQueryDevtools } from '@tanstack/query-devtools'

/**
* Internal token used to prevent double providing of devtools in child injectors
*/
const DEVTOOLS_PROVIDED = new InjectionToken('', {
factory: () => ({
isProvided: false,
}),
})

/**
* Internal token for providing devtools options
*/
const DEVTOOLS_OPTIONS_SIGNAL = new InjectionToken<Signal<DevtoolsOptions>>('')

/**
* Enables developer tools in Angular development builds.
*
* **Example**
*
* ```ts
* export const appConfig: ApplicationConfig = {
* providers: [
* provideTanStackQuery(new QueryClient(), withDevtools())
* ]
* }
* ```
* The devtools will be rendered in `<body>`.
*
* If you need more control over when devtools are loaded, you can use the `loadDevtools` option.
*
* If you need more control over where devtools are rendered, consider `injectDevtoolsPanel`. This allows rendering devtools inside your own devtools for example.
* @param withDevtoolsFn - A function that returns `DevtoolsOptions`.
* @param options - Additional options for configuring `withDevtools`.
* @returns A set of providers for use with `provideTanStackQuery`.
* @see {@link provideTanStackQuery}
* @see {@link DevtoolsOptions}
*/
export const withDevtools: WithDevtools = (
withDevtoolsFn?: WithDevtoolsFn,
options: WithDevtoolsOptions = {},
) =>
queryFeature('Devtools', [
{
provide: DEVTOOLS_OPTIONS_SIGNAL,
useFactory: (...deps: Array<any>) =>
computed(() => withDevtoolsFn?.(...deps) ?? {}),
deps: options.deps || [],
},
{
// Do not use provideEnvironmentInitializer while Angular < v19 is supported
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory: () => {
const devtoolsProvided = inject(DEVTOOLS_PROVIDED)
if (
!isPlatformBrowser(inject(PLATFORM_ID)) ||
devtoolsProvided.isProvided
)
return noop

devtoolsProvided.isProvided = true
let injectorIsDestroyed = false
inject(DestroyRef).onDestroy(() => (injectorIsDestroyed = true))

return () => {
const injectedClient = inject(QueryClient, {
optional: true,
})
const destroyRef = inject(DestroyRef)
const devtoolsOptions = inject(DEVTOOLS_OPTIONS_SIGNAL)
const injector = inject(Injector)

let devtools: TanstackQueryDevtools | null = null
let el: HTMLElement | null = null

const shouldLoadToolsSignal = computed(() => {
const { loadDevtools } = devtoolsOptions()
return typeof loadDevtools === 'boolean'
? loadDevtools
: isDevMode()
})

const getResolvedQueryClient = () => {
const client = devtoolsOptions().client ?? injectedClient
if (!client) {
throw new Error('No QueryClient found')
}
return client
}

const destroyDevtools = () => {
devtools?.unmount()
el?.remove()
devtools = null
}

effect(
() => {
const shouldLoadTools = shouldLoadToolsSignal()
const {
client,
position,
errorTypes,
buttonPosition,
initialIsOpen,
} = devtoolsOptions()

if (!shouldLoadTools) {
// Destroy or do nothing
devtools && destroyDevtools()
return
}

if (devtools) {
// Update existing devtools config
client && devtools.setClient(client)
position && devtools.setPosition(position)
errorTypes && devtools.setErrorTypes(errorTypes)
buttonPosition && devtools.setButtonPosition(buttonPosition)
typeof initialIsOpen === 'boolean' &&
devtools.setInitialIsOpen(initialIsOpen)
return
}

// Create devtools
import('@tanstack/query-devtools').then((queryDevtools) => {
// As this code runs async, injector can be destroyed in the meantime
if (injectorIsDestroyed) return

devtools = new queryDevtools.TanstackQueryDevtools({
...devtoolsOptions(),
client: getResolvedQueryClient(),
queryFlavor: 'Angular Query',
version: '5',
onlineManager,
})

el = document.body.appendChild(document.createElement('div'))
el.classList.add('tsqd-parent-container')
devtools.mount(el)

destroyRef.onDestroy(destroyDevtools)
})
},
{ injector },
)
}
},
},
])
7 changes: 2 additions & 5 deletions packages/angular-query-experimental/src/index.ts
Original file line number Diff line number Diff line change
@@ -48,17 +48,14 @@ export { injectQuery } from './inject-query'
export { injectQueryClient } from './inject-query-client'

export type {
DeveloperToolsFeature,
DevtoolsOptions,
DevtoolsFeature,
PersistQueryClientFeature,
QueryFeature,
QueryFeatureKind,
QueryFeatures,
} from './providers'
export {
provideAngularQuery,
provideQueryClient,
provideTanStackQuery,
queryFeature,
queryFeatures,
withDevtools,
} from './providers'
Original file line number Diff line number Diff line change
@@ -84,7 +84,6 @@ export type DefinedInitialDataInfiniteOptions<
* The `queryKey` will be tagged with the type from `queryFn`.
* @param options - The infinite query options to tag with the type from `queryFn`.
* @returns The tagged infinite query options.
* @public
*/
export function infiniteQueryOptions<
TQueryFnData,
@@ -116,7 +115,6 @@ export function infiniteQueryOptions<
* The `queryKey` will be tagged with the type from `queryFn`.
* @param options - The infinite query options to tag with the type from `queryFn`.
* @returns The tagged infinite query options.
* @public
*/
export function infiniteQueryOptions<
TQueryFnData,
@@ -148,7 +146,6 @@ export function infiniteQueryOptions<
* The `queryKey` will be tagged with the type from `queryFn`.
* @param options - The infinite query options to tag with the type from `queryFn`.
* @returns The tagged infinite query options.
* @public
*/
export function infiniteQueryOptions<
TQueryFnData,
@@ -180,7 +177,6 @@ export function infiniteQueryOptions<
* The `queryKey` will be tagged with the type from `queryFn`.
* @param options - The infinite query options to tag with the type from `queryFn`.
* @returns The tagged infinite query options.
* @public
*/
export function infiniteQueryOptions(options: unknown) {
return options
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@ export interface InjectInfiniteQueryOptions {
* @param injectInfiniteQueryFn - A function that returns infinite query options.
* @param options - Additional configuration.
* @returns The infinite query result.
* @public
*/
export function injectInfiniteQuery<
TQueryFnData,
@@ -62,7 +61,6 @@ export function injectInfiniteQuery<
* @param injectInfiniteQueryFn - A function that returns infinite query options.
* @param options - Additional configuration.
* @returns The infinite query result.
* @public
*/
export function injectInfiniteQuery<
TQueryFnData,
@@ -87,7 +85,6 @@ export function injectInfiniteQuery<
* @param injectInfiniteQueryFn - A function that returns infinite query options.
* @param options - Additional configuration.
* @returns The infinite query result.
* @public
*/
export function injectInfiniteQuery<
TQueryFnData,
@@ -112,7 +109,6 @@ export function injectInfiniteQuery<
* @param injectInfiniteQueryFn - A function that returns infinite query options.
* @param options - Additional configuration.
* @returns The infinite query result.
* @public
*/
export function injectInfiniteQuery(
injectInfiniteQueryFn: () => CreateInfiniteQueryOptions,
Original file line number Diff line number Diff line change
@@ -26,7 +26,6 @@ export interface InjectIsMutatingOptions {
* @param filters - The filters to apply to the query.
* @param options - Additional configuration
* @returns signal with number of fetching mutations.
* @public
*/
export function injectIsMutating(
filters?: MutationFilters,
28 changes: 12 additions & 16 deletions packages/angular-query-experimental/src/inject-is-restoring.ts
Original file line number Diff line number Diff line change
@@ -7,30 +7,27 @@ import {
} from '@angular/core'
import type { Provider, Signal } from '@angular/core'

const IS_RESTORING = new InjectionToken(
typeof ngDevMode === 'undefined' || ngDevMode
? 'TANSTACK_QUERY_IS_RESTORING'
: '',
{
// Default value when not provided
factory: () => signal(false).asReadonly(),
},
)

/**
* The `Injector` in which to create the isRestoring signal.
*
* If this is not provided, the current injection context will be used instead (via `inject`).
* Internal token used to track isRestoring state, accessible in public API through `injectIsRestoring` and set via `provideIsRestoring`
*/
const IS_RESTORING = new InjectionToken('', {
// Default value when not provided
factory: () => signal(false).asReadonly(),
})

interface InjectIsRestoringOptions {
/**
* The `Injector` to use to get the isRestoring signal.
*
* If this is not provided, the current injection context will be used instead (via `inject`).
*/
injector?: Injector
}

/**
* Injects a signal that tracks whether a restore is currently in progress. {@link injectQuery} and friends also check this internally to avoid race conditions between the restore and initializing queries.
* @param options - Options for injectIsRestoring.
* @returns signal with boolean that indicates whether a restore is in progress.
* @public
* @returns readonly signal with boolean that indicates whether a restore is in progress.
*/
export function injectIsRestoring(options?: InjectIsRestoringOptions) {
!options?.injector && assertInInjectionContext(injectIsRestoring)
@@ -42,7 +39,6 @@ export function injectIsRestoring(options?: InjectIsRestoringOptions) {
* Used by TanStack Query Angular persist client plugin to provide the signal that tracks the restore state
* @param isRestoring - a readonly signal that returns a boolean
* @returns Provider for the `isRestoring` signal
* @public
*/
export function provideIsRestoring(isRestoring: Signal<boolean>): Provider {
return {
Original file line number Diff line number Diff line change
@@ -42,9 +42,6 @@ function getResult<TResult = MutationState>(
)
}

/**
* @public
*/
export interface InjectMutationStateOptions {
/**
* The `Injector` in which to create the mutation state signal.
@@ -59,7 +56,6 @@ export interface InjectMutationStateOptions {
* @param injectMutationStateFn - A function that returns mutation state options.
* @param options - The Angular injector to use.
* @returns The signal that tracks the state of all mutations.
* @public
*/
export function injectMutationState<TResult = MutationState>(
injectMutationStateFn: () => MutationStateOptions<TResult> = () => ({}),
1 change: 0 additions & 1 deletion packages/angular-query-experimental/src/inject-mutation.ts
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@ export interface InjectMutationOptions {
* @param injectMutationFn - A function that returns mutation options.
* @param options - Additional configuration
* @returns The mutation.
* @public
*/
export function injectMutation<
TData = unknown,
4 changes: 0 additions & 4 deletions packages/angular-query-experimental/src/inject-queries.ts
Original file line number Diff line number Diff line change
@@ -116,7 +116,6 @@ type GetResults<T> =

/**
* QueriesOptions reducer recursively unwraps function arguments to infer/enforce type param
* @public
*/
export type QueriesOptions<
T extends Array<any>,
@@ -159,7 +158,6 @@ export type QueriesOptions<

/**
* QueriesResults reducer recursively maps type param to results
* @public
*/
export type QueriesResults<
T extends Array<any>,
@@ -200,8 +198,6 @@ export type QueriesResults<
* @param root0.queries
* @param root0.combine
* @param injector
* @param injector
* @public
*/
export function injectQueries<
T extends Array<any>,
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@ import type { InjectOptions } from '@angular/core'
* Injects a `QueryClient` instance and allows passing a custom injector.
* @param injectOptions - Type of the options argument to inject and optionally a custom injector.
* @returns The `QueryClient` instance.
* @public
* @deprecated Use `inject(QueryClient)` instead.
* If you need to get a `QueryClient` from a custom injector, use `injector.get(QueryClient)`.
*
4 changes: 0 additions & 4 deletions packages/angular-query-experimental/src/inject-query.ts
Original file line number Diff line number Diff line change
@@ -60,7 +60,6 @@ export interface InjectQueryOptions {
* @param injectQueryFn - A function that returns query options.
* @param options - Additional configuration
* @returns The query result.
* @public
* @see https://tanstack.com/query/latest/docs/framework/angular/guides/queries
*/
export function injectQuery<
@@ -112,7 +111,6 @@ export function injectQuery<
* @param injectQueryFn - A function that returns query options.
* @param options - Additional configuration
* @returns The query result.
* @public
* @see https://tanstack.com/query/latest/docs/framework/angular/guides/queries
*/
export function injectQuery<
@@ -164,7 +162,6 @@ export function injectQuery<
* @param injectQueryFn - A function that returns query options.
* @param options - Additional configuration
* @returns The query result.
* @public
* @see https://tanstack.com/query/latest/docs/framework/angular/guides/queries
*/
export function injectQuery<
@@ -216,7 +213,6 @@ export function injectQuery<
* @param injectQueryFn - A function that returns query options.
* @param options - Additional configuration
* @returns The query result.
* @public
* @see https://tanstack.com/query/latest/docs/framework/angular/guides/queries
*/
export function injectQuery(
4 changes: 0 additions & 4 deletions packages/angular-query-experimental/src/mutation-options.ts
Original file line number Diff line number Diff line change
@@ -33,7 +33,6 @@ import type {
* ```
* @param options - The mutation options.
* @returns Mutation options.
* @public
*/
export function mutationOptions<
TData = unknown,
@@ -46,9 +45,6 @@ export function mutationOptions<
return options
}

/**
* @public
*/
export interface CreateMutationOptions<
TData = unknown,
TError = DefaultError,
219 changes: 12 additions & 207 deletions packages/angular-query-experimental/src/providers.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
import {
DestroyRef,
ENVIRONMENT_INITIALIZER,
InjectionToken,
PLATFORM_ID,
computed,
effect,
inject,
} from '@angular/core'
import { QueryClient, noop, onlineManager } from '@tanstack/query-core'
import { isPlatformBrowser } from '@angular/common'
import { isDevMode } from './util/is-dev-mode/is-dev-mode'
import { DestroyRef, InjectionToken, inject } from '@angular/core'
import { QueryClient } from '@tanstack/query-core'
import type { Provider } from '@angular/core'
import type {
DevtoolsButtonPosition,
DevtoolsErrorType,
DevtoolsPosition,
TanstackQueryDevtools,
} from '@tanstack/query-devtools'

/**
* Usually {@link provideTanStackQuery} is used once to set up TanStack Query and the
@@ -108,6 +92,10 @@ export function provideQueryClient(
* // In a lazy loaded route or lazy loaded component's providers array:
* providers: [provideTanStackQuery(MY_QUERY_CLIENT)]
* ```
* Using an InjectionToken for the QueryClient is an advanced optimization which allows TanStack Query to be absent from the main application bundle.
* This can be beneficial if you want to include TanStack Query on lazy loaded routes only while still sharing a `QueryClient`.
*
* Note that this is a small optimization and for most applications it's preferable to provide the `QueryClient` in the main application config.
* @param queryClient - A `QueryClient` instance, or an `InjectionToken` which provides a `QueryClient`.
* @param features - Optional features to configure additional Query functionality.
* @returns A set of providers to set up TanStack Query.
@@ -130,14 +118,17 @@ export function provideTanStackQuery(
* Allows to configure a `QueryClient`.
* @param queryClient - A `QueryClient` instance.
* @returns A set of providers to set up TanStack Query.
* @public
* @see https://tanstack.com/query/v5/docs/framework/angular/quick-start
* @deprecated Use `provideTanStackQuery` instead.
*/
export function provideAngularQuery(queryClient: QueryClient): Array<Provider> {
return provideTanStackQuery(queryClient)
}

const queryFeatures = ['Devtools', 'PersistQueryClient'] as const

type QueryFeatureKind = (typeof queryFeatures)[number]

/**
* Helper type to represent a Query feature.
*/
@@ -162,207 +153,21 @@ export function queryFeature<TFeatureKind extends QueryFeatureKind>(
/**
* A type alias that represents a feature which enables developer tools.
* The type is used to describe the return value of the `withDevtools` function.
* @public
* @see {@link withDevtools}
*/
export type DeveloperToolsFeature = QueryFeature<'DeveloperTools'>
export type DevtoolsFeature = QueryFeature<'Devtools'>

/**
* A type alias that represents a feature which enables persistence.
* The type is used to describe the return value of the `withPersistQueryClient` function.
* @public
*/
export type PersistQueryClientFeature = QueryFeature<'PersistQueryClient'>

/**
* Options for configuring the TanStack Query devtools.
* @public
*/
export interface DevtoolsOptions {
/**
* Set this true if you want the devtools to default to being open
*/
initialIsOpen?: boolean
/**
* The position of the TanStack logo to open and close the devtools panel.
* `top-left` | `top-right` | `bottom-left` | `bottom-right` | `relative`
* Defaults to `bottom-right`.
*/
buttonPosition?: DevtoolsButtonPosition
/**
* The position of the TanStack Query devtools panel.
* `top` | `bottom` | `left` | `right`
* Defaults to `bottom`.
*/
position?: DevtoolsPosition
/**
* Custom instance of QueryClient
*/
client?: QueryClient
/**
* Use this so you can define custom errors that can be shown in the devtools.
*/
errorTypes?: Array<DevtoolsErrorType>
/**
* Use this to pass a nonce to the style tag that is added to the document head. This is useful if you are using a Content Security Policy (CSP) nonce to allow inline styles.
*/
styleNonce?: string
/**
* Use this so you can attach the devtool's styles to a specific element in the DOM.
*/
shadowDOMTarget?: ShadowRoot

/**
* Whether the developer tools should load.
* - `auto`- (Default) Lazily loads devtools when in development mode. Skips loading in production mode.
* - `true`- Always load the devtools, regardless of the environment.
* - `false`- Never load the devtools, regardless of the environment.
*
* You can use `true` and `false` to override loading developer tools from an environment file.
* For example, a test environment might run in production mode but you may want to load developer tools.
*
* Additionally, you can use a signal in the callback to dynamically load the devtools based on a condition. For example,
* a signal created from a RxJS observable that listens for a keyboard shortcut.
*
* **Example**
* ```ts
* withDevtools(() => ({
* initialIsOpen: true,
* loadDevtools: inject(ExampleService).loadDevtools()
* }))
* ```
*/
loadDevtools?: 'auto' | boolean
}

/**
* Enables developer tools.
*
* **Example**
*
* ```ts
* export const appConfig: ApplicationConfig = {
* providers: [
* provideTanStackQuery(new QueryClient(), withDevtools())
* ]
* }
* ```
* By default the devtools will be loaded when Angular runs in development mode and rendered in `<body>`.
*
* If you need more control over when devtools are loaded, you can use the `loadDevtools` option. This is particularly useful if you want to load devtools based on environment configurations. For instance, you might have a test environment running in production mode but still require devtools to be available.
*
* If you need more control over where devtools are rendered, consider `injectDevtoolsPanel`. This allows rendering devtools inside your own devtools for example.
* @param withDevtoolsFn - A function that returns `DevtoolsOptions`.
* @returns A set of providers for use with `provideTanStackQuery`.
* @public
* @see {@link provideTanStackQuery}
* @see {@link DevtoolsOptions}
*/
export function withDevtools(
withDevtoolsFn?: () => DevtoolsOptions,
): DeveloperToolsFeature {
let providers: Array<Provider> = []
if (!isDevMode() && !withDevtoolsFn) {
providers = []
} else {
providers = [
{
// Do not use provideEnvironmentInitializer while Angular < v19 is supported
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory: () => {
if (!isPlatformBrowser(inject(PLATFORM_ID))) return noop
const injectedClient = inject(QueryClient, {
optional: true,
})
const destroyRef = inject(DestroyRef)

const options = computed(() => withDevtoolsFn?.() ?? {})

let devtools: TanstackQueryDevtools | null = null
let el: HTMLElement | null = null

const shouldLoadToolsSignal = computed(() => {
const { loadDevtools } = options()
return typeof loadDevtools === 'boolean'
? loadDevtools
: isDevMode()
})

const getResolvedQueryClient = () => {
const client = options().client ?? injectedClient
if (!client) {
throw new Error('No QueryClient found')
}
return client
}

const destroyDevtools = () => {
devtools?.unmount()
el?.remove()
devtools = null
}

return () =>
effect(() => {
const shouldLoadTools = shouldLoadToolsSignal()
const {
client,
position,
errorTypes,
buttonPosition,
initialIsOpen,
} = options()

if (devtools && !shouldLoadTools) {
destroyDevtools()
return
} else if (devtools && shouldLoadTools) {
client && devtools.setClient(client)
position && devtools.setPosition(position)
errorTypes && devtools.setErrorTypes(errorTypes)
buttonPosition && devtools.setButtonPosition(buttonPosition)
initialIsOpen && devtools.setInitialIsOpen(initialIsOpen)
return
} else if (!shouldLoadTools) {
return
}

el = document.body.appendChild(document.createElement('div'))
el.classList.add('tsqd-parent-container')

import('@tanstack/query-devtools').then((queryDevtools) => {
devtools = new queryDevtools.TanstackQueryDevtools({
...options(),
client: getResolvedQueryClient(),
queryFlavor: 'Angular Query',
version: '5',
onlineManager,
})

el && devtools.mount(el)

// Unmount the devtools on application destroy
destroyRef.onDestroy(destroyDevtools)
})
})
},
},
]
}
return queryFeature('DeveloperTools', providers)
}

/**
* A type alias that represents all Query features available for use with `provideTanStackQuery`.
* Features can be enabled by adding special functions to the `provideTanStackQuery` call.
* See documentation for each symbol to find corresponding function name. See also `provideTanStackQuery`
* documentation on how to use those functions.
* @public
* @see {@link provideTanStackQuery}
*/
export type QueryFeatures = DeveloperToolsFeature | PersistQueryClientFeature

export const queryFeatures = ['DeveloperTools', 'PersistQueryClient'] as const

export type QueryFeatureKind = (typeof queryFeatures)[number]
export type QueryFeatures = DevtoolsFeature | PersistQueryClientFeature
4 changes: 0 additions & 4 deletions packages/angular-query-experimental/src/query-options.ts
Original file line number Diff line number Diff line change
@@ -72,7 +72,6 @@ export type DefinedInitialDataOptions<
* ```
* @param options - The query options to tag with the type from `queryFn`.
* @returns The tagged query options.
* @public
*/
export function queryOptions<
TQueryFnData = unknown,
@@ -105,7 +104,6 @@ export function queryOptions<
* ```
* @param options - The query options to tag with the type from `queryFn`.
* @returns The tagged query options.
* @public
*/
export function queryOptions<
TQueryFnData = unknown,
@@ -138,7 +136,6 @@ export function queryOptions<
* ```
* @param options - The query options to tag with the type from `queryFn`.
* @returns The tagged query options.
* @public
*/
export function queryOptions<
TQueryFnData = unknown,
@@ -171,7 +168,6 @@ export function queryOptions<
* ```
* @param options - The query options to tag with the type from `queryFn`.
* @returns The tagged query options.
* @public
*/
export function queryOptions(options: unknown) {
return options
48 changes: 0 additions & 48 deletions packages/angular-query-experimental/src/types.ts
Original file line number Diff line number Diff line change
@@ -17,9 +17,6 @@ import type {
import type { Signal } from '@angular/core'
import type { MapToSignals } from './signal-proxy'

/**
* @public
*/
export interface CreateBaseQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
@@ -34,9 +31,6 @@ export interface CreateBaseQueryOptions<
TQueryKey
> {}

/**
* @public
*/
export interface CreateQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
@@ -53,18 +47,12 @@ export interface CreateQueryOptions<
'suspense'
> {}

/**
* @public
*/
type CreateStatusBasedQueryResult<
TStatus extends QueryObserverResult['status'],
TData = unknown,
TError = DefaultError,
> = Extract<QueryObserverResult<TData, TError>, { status: TStatus }>

/**
* @public
*/
export interface BaseQueryNarrowing<TData = unknown, TError = DefaultError> {
isSuccess: (
this: CreateBaseQueryResult<TData, TError>,
@@ -89,9 +77,6 @@ export interface BaseQueryNarrowing<TData = unknown, TError = DefaultError> {
>
}

/**
* @public
*/
export interface CreateInfiniteQueryOptions<
TQueryFnData = unknown,
TError = DefaultError,
@@ -109,46 +94,31 @@ export interface CreateInfiniteQueryOptions<
'suspense'
> {}

/**
* @public
*/
export type CreateBaseQueryResult<
TData = unknown,
TError = DefaultError,
TState = QueryObserverResult<TData, TError>,
> = BaseQueryNarrowing<TData, TError> &
MapToSignals<OmitKeyof<TState, keyof BaseQueryNarrowing, 'safely'>>

/**
* @public
*/
export type CreateQueryResult<
TData = unknown,
TError = DefaultError,
> = CreateBaseQueryResult<TData, TError>

/**
* @public
*/
export type DefinedCreateQueryResult<
TData = unknown,
TError = DefaultError,
TState = DefinedQueryObserverResult<TData, TError>,
> = BaseQueryNarrowing<TData, TError> &
MapToSignals<OmitKeyof<TState, keyof BaseQueryNarrowing, 'safely'>>

/**
* @public
*/
export type CreateInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
> = BaseQueryNarrowing<TData, TError> &
MapToSignals<InfiniteQueryObserverResult<TData, TError>>

/**
* @public
*/
export type DefinedCreateInfiniteQueryResult<
TData = unknown,
TError = DefaultError,
@@ -158,9 +128,6 @@ export type DefinedCreateInfiniteQueryResult<
>,
> = MapToSignals<TDefinedInfiniteQueryObserver>

/**
* @public
*/
export type CreateMutateFunction<
TData = unknown,
TError = DefaultError,
@@ -170,19 +137,13 @@ export type CreateMutateFunction<
...args: Parameters<MutateFunction<TData, TError, TVariables, TContext>>
) => void

/**
* @public
*/
export type CreateMutateAsyncFunction<
TData = unknown,
TError = DefaultError,
TVariables = void,
TContext = unknown,
> = MutateFunction<TData, TError, TVariables, TContext>

/**
* @public
*/
export type CreateBaseMutationResult<
TData = unknown,
TError = DefaultError,
@@ -195,9 +156,6 @@ export type CreateBaseMutationResult<
mutateAsync: CreateMutateAsyncFunction<TData, TError, TVariables, TContext>
}

/**
* @public
*/
type CreateStatusBasedMutationResult<
TStatus extends CreateBaseMutationResult['status'],
TData = unknown,
@@ -211,9 +169,6 @@ type CreateStatusBasedMutationResult<

type SignalFunction<T extends () => any> = T & Signal<ReturnType<T>>

/**
* @public
*/
export interface BaseMutationNarrowing<
TData = unknown,
TError = DefaultError,
@@ -290,9 +245,6 @@ export interface BaseMutationNarrowing<
>
}

/**
* @public
*/
export type CreateMutationResult<
TData = unknown,
TError = DefaultError,

This file was deleted.

10 changes: 7 additions & 3 deletions packages/angular-query-experimental/vite.config.ts
Original file line number Diff line number Diff line change
@@ -76,8 +76,6 @@ export const tanstackViteConfig = (options: Options) => {
declarationMap: false,
},
beforeWriteFile: (filePath, content) => {
// content =
// options.beforeWriteDeclarationFile?.(filePath, content) || content
return {
filePath,
content: ensureImportFileExtension({ content, extension: 'js' }),
@@ -114,7 +112,13 @@ export default mergeConfig(
config,
tanstackViteConfig({
cjs: false,
entry: ['./src/index.ts'],
entry: [
'./src/index.ts',
'./src/devtools-panel/index.ts',
'./src/devtools-panel/stub.ts',
'./src/devtools/index.ts',
'./src/devtools/stub.ts',
],
exclude: ['src/__tests__'],
srcDir: './src',
tsconfigPath: 'tsconfig.prod.json',
4 changes: 2 additions & 2 deletions packages/angular-query-persist-client/package.json
Original file line number Diff line number Diff line change
@@ -26,8 +26,8 @@
"test:types:ts53": "node ../../node_modules/typescript53/lib/tsc.js --build",
"test:types:ts54": "node ../../node_modules/typescript54/lib/tsc.js --build",
"test:types:ts55": "node ../../node_modules/typescript55/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build",
"test:types:ts56": "node ../../node_modules/typescript56/lib/tsc.js --build",
"test:types:ts57": "node ../../node_modules/typescript57/lib/tsc.js --build",
"test:types:tscurrent": "tsc --build",
"test:lib": "vitest",
"test:lib:dev": "pnpm run test:lib --watch",
65 changes: 0 additions & 65 deletions pnpm-lock.yaml
4 changes: 0 additions & 4 deletions scripts/publish.js
Original file line number Diff line number Diff line change
@@ -84,10 +84,6 @@ await publish({
name: '@tanstack/vue-query-devtools',
packageDir: 'packages/vue-query-devtools',
},
{
name: '@tanstack/angular-query-devtools-experimental',
packageDir: 'packages/angular-query-devtools-experimental',
},
{
name: '@tanstack/angular-query-experimental',
packageDir: 'packages/angular-query-experimental',