Skip to content

Commit bce83f9

Browse files
authored
fix(angular-query-devtools): Reimplement Angular devtools (TanStack#6722)
* fix(angular-query-devtools): Reimplement Angular devtools Skip hydration Core devtools handles defaults Enable all core devtools functionality Add angular-query as peer dependency
1 parent af1004b commit bce83f9

File tree

3 files changed

+101
-95
lines changed

3 files changed

+101
-95
lines changed

docs/angular/devtools.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,7 @@ import { Component } from '@angular/core';
5353
- The position of the Angular Query devtools panel
5454
- `client?: QueryClient`,
5555
- Use this to use a custom QueryClient. Otherwise, the QueryClient provided through provideAngularQuery() will be injected.
56+
- `errorTypes?: { name: string; initializer: (query: Query) => TError}`
57+
- Use this to predefine some errors that can be triggered on your queries. Initializer will be called (with the specific query) when that error is toggled on from the UI. It must return an Error.
58+
- `styleNonce?: string`
59+
- 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.

packages/angular-query-devtools-experimental/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@
4040
"zone.js": "^0.14.2"
4141
},
4242
"peerDependencies": {
43-
"@angular/core": "^17"
43+
"@angular/core": "^17",
44+
"@tanstack/angular-query-experimental": "workspace:^"
4445
},
4546
"module": "build/fesm2022/tanstack-angular-query-devtools-experimental.mjs",
4647
"types": "build/index.d.ts",
Lines changed: 95 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,141 @@
1-
import { TanstackQueryDevtools } from '@tanstack/query-devtools'
1+
import * as queryDevtools from '@tanstack/query-devtools'
2+
import {
3+
injectQueryClient,
4+
onlineManager,
5+
} from '@tanstack/angular-query-experimental'
26
import {
37
ChangeDetectionStrategy,
48
Component,
59
ElementRef,
610
Input,
711
ViewChild,
812
booleanAttribute,
9-
inject,
1013
} from '@angular/core'
11-
import {
12-
QUERY_CLIENT,
13-
onlineManager,
14-
} from '@tanstack/angular-query-experimental'
15-
import type { QueryClient } from '@tanstack/angular-query-experimental'
16-
import type { AfterViewInit, OnDestroy } from '@angular/core'
14+
import { QueryClient } from '@tanstack/angular-query-experimental'
15+
import type {
16+
AfterViewInit,
17+
OnChanges,
18+
OnDestroy,
19+
SimpleChanges,
20+
} from '@angular/core'
1721
import type {
18-
// DevToolsErrorType as DevToolsErrorTypeOriginal,
19-
DevtoolsButtonPosition as DevtoolsButtonPositionOriginal,
20-
DevtoolsPosition as DevtoolsPositionOriginal,
22+
DevToolsErrorType,
23+
TanstackQueryDevtools,
2124
} from '@tanstack/query-devtools'
2225

23-
// Alias types for decorators
24-
type DevtoolsButtonPosition = DevtoolsButtonPositionOriginal
25-
type DevtoolsPosition = DevtoolsPositionOriginal
26-
// type DevToolsErrorType = DevToolsErrorTypeOriginal
27-
2826
@Component({
2927
selector: 'angular-query-devtools',
3028
standalone: true,
3129
template: `<div class="tsqd-parent-container" #ref></div>`,
3230
changeDetection: ChangeDetectionStrategy.OnPush,
31+
host: { ngSkipHydration: 'true' },
3332
})
34-
export class AngularQueryDevtools implements AfterViewInit, OnDestroy {
35-
readonly #injectedClient = inject<QueryClient>(QUERY_CLIENT, {
36-
optional: true,
37-
})
33+
export class AngularQueryDevtools
34+
implements AfterViewInit, OnChanges, OnDestroy
35+
{
36+
/*
37+
* It is intentional that there are no default values on inputs.
38+
* Core devtools will set defaults when values are undefined.
39+
* */
3840

39-
#clientFromAttribute: QueryClient | null = null
40-
41-
#getAppliedQueryClient() {
42-
if (!this.#clientFromAttribute && !this.#injectedClient) {
43-
throw new Error(
44-
`You must either provide a client via 'provideAngularQuery' or pass it to the 'client' attribute of angular-query-devtools.`,
45-
)
46-
}
47-
return this.#clientFromAttribute ?? this.#injectedClient
48-
}
49-
50-
@ViewChild('ref') ref!: ElementRef
51-
52-
#devtools: TanstackQueryDevtools
53-
54-
constructor() {
55-
this.#devtools = new TanstackQueryDevtools({
56-
client: this.#getAppliedQueryClient()!,
57-
queryFlavor: 'Angular Query',
58-
version: '5',
59-
onlineManager,
60-
buttonPosition: this.buttonPosition,
61-
position: this.position,
62-
initialIsOpen: this.initialIsOpen,
63-
// errorTypes,
64-
})
65-
}
66-
67-
#initialIsOpen = false
6841
/**
6942
* Add this attribute if you want the dev tools to default to being open
43+
* @example
44+
* <angular-query-devtools initialIsOpen />
7045
*/
71-
@Input({ transform: booleanAttribute })
72-
set initialIsOpen(value: boolean) {
73-
this.#initialIsOpen = value
74-
this.#devtools.setInitialIsOpen(value)
75-
}
76-
get initialIsOpen() {
77-
return this.#initialIsOpen
78-
}
46+
@Input({ transform: booleanAttribute }) initialIsOpen?: boolean
7947

80-
#buttonPosition: DevtoolsButtonPosition = 'bottom-left'
8148
/**
8249
* The position of the TanStack logo to open and close the devtools panel.
8350
* 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
84-
* Defaults to 'bottom-left'.
51+
* Defaults to 'bottom-right'.
52+
* @example
53+
* <angular-query-devtools buttonPosition="top-right" />
8554
*/
86-
@Input()
87-
set buttonPosition(value: DevtoolsButtonPosition) {
88-
this.#buttonPosition = value
89-
this.#devtools.setButtonPosition(value)
90-
}
91-
get buttonPosition() {
92-
return this.#buttonPosition
93-
}
55+
@Input() buttonPosition?: queryDevtools.DevtoolsButtonPosition
9456

95-
#position: DevtoolsPosition = 'bottom'
9657
/**
9758
* The position of the Angular Query devtools panel.
9859
* 'top' | 'bottom' | 'left' | 'right'
99-
* Defaults to 'bottom'.
60+
* @example
61+
* <angular-query-devtools position="bottom" />
10062
*/
101-
@Input()
102-
set position(value: DevtoolsPosition) {
103-
this.#position = value
104-
this.#devtools.setPosition(value)
105-
}
106-
get position() {
107-
return this.#position
108-
}
63+
@Input({ alias: 'position' }) position?: queryDevtools.DevtoolsPosition
10964

11065
/**
11166
* Custom instance of QueryClient
67+
* @example
68+
* <angular-query-devtools [client]="queryClient" />
11269
*/
113-
@Input()
114-
set client(client: QueryClient | undefined) {
115-
this.#clientFromAttribute = client ?? null
116-
this.#devtools.setClient(this.#getAppliedQueryClient()!)
117-
}
70+
@Input() client?: QueryClient
71+
72+
/**
73+
* 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.
74+
* @example
75+
* <angular-query-devtools styleNonce="YourRandomNonceValue" />
76+
*/
77+
@Input() styleNonce?: string
11878

119-
// TODO: needs to tested. When re-adding don't forget to re-add to devtools.md too
120-
// #errorTypes: Array<DevToolsErrorType> = []
12179
/**
12280
* Use this so you can define custom errors that can be shown in the devtools.
12381
*/
124-
// @Input()
125-
// set errorTypes(value: Array<DevToolsErrorType>) {
126-
// this.#errorTypes = value
127-
// this.#devtools?.setErrorTypes(value)
128-
// }
129-
// get errorTypes(): Array<DevToolsErrorType> {
130-
// return this.#errorTypes
131-
// }
82+
@Input() errorTypes?: Array<DevToolsErrorType>
83+
84+
@ViewChild('ref') ref!: ElementRef
85+
86+
#devtools?: TanstackQueryDevtools
87+
88+
readonly #injectedClient: QueryClient | null = injectQueryClient({
89+
optional: true,
90+
})
91+
92+
ngOnChanges(changes: SimpleChanges) {
93+
if (!this.#devtools) return
94+
if (changes['client']) {
95+
this.#devtools.setClient(this.#getAppliedQueryClient())
96+
}
97+
if (changes['buttonPosition'] && this.buttonPosition) {
98+
this.#devtools.setButtonPosition(this.buttonPosition)
99+
}
100+
if (changes['position'] && this.position) {
101+
this.#devtools.setPosition(this.position)
102+
}
103+
if (changes['initialIsOpen'] && this.initialIsOpen) {
104+
this.#devtools.setInitialIsOpen(this.initialIsOpen)
105+
}
106+
if (changes['errorTypes'] && this.errorTypes) {
107+
this.#devtools.setErrorTypes(this.errorTypes)
108+
}
109+
}
132110

133111
ngAfterViewInit() {
134-
this.#devtools.mount(this.ref.nativeElement)
112+
const devtools = new queryDevtools.TanstackQueryDevtools({
113+
client: this.#getAppliedQueryClient(),
114+
queryFlavor: 'Angular Query',
115+
version: '5',
116+
onlineManager,
117+
buttonPosition: this.buttonPosition,
118+
position: this.position,
119+
initialIsOpen: this.initialIsOpen,
120+
errorTypes: this.errorTypes,
121+
styleNonce: this.styleNonce,
122+
})
123+
devtools.mount(this.ref.nativeElement)
124+
this.#devtools = devtools
135125
}
136126

137127
ngOnDestroy() {
138-
this.#devtools.unmount()
128+
this.#devtools?.unmount()
129+
}
130+
131+
#getAppliedQueryClient() {
132+
const client = this.client ?? this.#injectedClient
133+
if (!client) {
134+
throw new Error(
135+
'You must either provide a client via `provideAngularQuery` ' +
136+
'or pass it to the `client` attribute of `<angular-query-devtools>`.',
137+
)
138+
}
139+
return client
139140
}
140141
}

0 commit comments

Comments
 (0)