Skip to content

Commit 967c231

Browse files
OmerGronichautofix-ci[bot]arnoud-dv
authored
feat(angular-query): add angular-persist-query-client
* feat(angular-query): - Created isRestoring injection token and provider - handled restoration phase in create-base-query.ts - handled restoration phase in * feat(angular-query): created package for experimental persistence support, and the withPersistQueryClient feature * test(persistQuery): add tests for withPersistQueryClient * docs(angular-query): add basic persister example * fix(angular-query): synced angular versions in new package * fix(angular-query): commit before ng update * feat(angular-query): updated example to v18 * fix(angular-query): fixed project name to basic-persister * feat(angular-query): updated example basic-persister to v19 * feat(angular-query): included back the persister package after angular migration * feat(angular-query): migrate to provideEnvironmentInitializer * fix(angular-query): fix eslint issues in inject-queries.ts * Revert "feat(angular-query): migrate to provideEnvironmentInitializer" This reverts commit af5b47b. * fix(angular-query): only track the isRestoring in the effect * fix(angular-query): removed conditional _isRestoring * ci: apply automated fixes * fix(angular-query): used effect onCleanup instead of destroy ref * Update examples/angular/basic-persister/.devcontainer/devcontainer.json Co-authored-by: Arnoud <[email protected]> * Update examples/angular/basic-persister/package.json Co-authored-by: Arnoud <[email protected]> * Update examples/angular/basic-persister/src/index.html Co-authored-by: Arnoud <[email protected]> * Update examples/angular/basic-persister/tsconfig.json Co-authored-by: Arnoud <[email protected]> * Update examples/angular/basic-persister/tsconfig.json Co-authored-by: Arnoud <[email protected]> * Update examples/angular/basic-persister/tsconfig.json Co-authored-by: Arnoud <[email protected]> * Update packages/angular-query-experimental/src/inject-is-restoring.ts Co-authored-by: Arnoud <[email protected]> * fix(angular-query): fixed lock file * fix(angular-query): aligned package.json versions * added missing dep from basic-persister example * Added a more complex example for persistence. This examples demonstrates how to selectively persist queries to different persisters. * fix(angular-query) bumped versions to 5.62.4 * refactor(angular-query) refactored to implicit return * fix(angular-query) bump angular persister package and examples version * fix(angular-query) fixed eslint errors * refactor(angular-query) set initial `isRestoring` to true to match the React adapter logic * Revert "refactor(angular-query) set initial `isRestoring` to true to match the React adapter logic" This reverts commit 235bbab. * fix(angular-query) fixed formatting of JSDOC comment ruined by eslint * update build configs * zoneless unit tests * replace deprecated injectQueryClient * createBaseQuery is always run in injection context, remove inject(Injector) * remove api extractor config * update example dependencies * comment out from publish script for now * refactor(persist-query-client): simplify configuration by removing array structure for persistOptions * refactor(angular-query-persist-client): removed experimental from package name and updated configurations * fix example * fix outdated comment * exclude from preview for now * mark package as private instead * configure injectIsRestoring injector through options object --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Arnoud <[email protected]>
1 parent 3471d0f commit 967c231

39 files changed

+1375
-74
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "Node.js",
3+
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
4+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {}
5+
6+
module.exports = config
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# TanStack Query Angular basic persister example
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn` or `pnpm i` or `bun i`
6+
- `npm run start` or `yarn start` or `pnpm start` or `bun start`
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
{
2+
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3+
"version": 1,
4+
"cli": {
5+
"packageManager": "pnpm",
6+
"analytics": false,
7+
"cache": {
8+
"enabled": false
9+
}
10+
},
11+
"newProjectRoot": "projects",
12+
"projects": {
13+
"basic-persister": {
14+
"projectType": "application",
15+
"schematics": {
16+
"@schematics/angular:component": {
17+
"inlineTemplate": true,
18+
"inlineStyle": true,
19+
"skipTests": true
20+
},
21+
"@schematics/angular:class": {
22+
"skipTests": true
23+
},
24+
"@schematics/angular:directive": {
25+
"skipTests": true
26+
},
27+
"@schematics/angular:guard": {
28+
"skipTests": true
29+
},
30+
"@schematics/angular:interceptor": {
31+
"skipTests": true
32+
},
33+
"@schematics/angular:pipe": {
34+
"skipTests": true
35+
},
36+
"@schematics/angular:resolver": {
37+
"skipTests": true
38+
},
39+
"@schematics/angular:service": {
40+
"skipTests": true
41+
}
42+
},
43+
"root": "",
44+
"sourceRoot": "src",
45+
"prefix": "app",
46+
"architect": {
47+
"build": {
48+
"builder": "@angular/build:application",
49+
"options": {
50+
"outputPath": "dist/basic-persister",
51+
"index": "src/index.html",
52+
"browser": "src/main.ts",
53+
"polyfills": ["zone.js"],
54+
"tsConfig": "tsconfig.app.json",
55+
"assets": ["src/favicon.ico", "src/assets"],
56+
"styles": [],
57+
"scripts": []
58+
},
59+
"configurations": {
60+
"production": {
61+
"budgets": [
62+
{
63+
"type": "initial",
64+
"maximumWarning": "500kb",
65+
"maximumError": "1mb"
66+
},
67+
{
68+
"type": "anyComponentStyle",
69+
"maximumWarning": "2kb",
70+
"maximumError": "4kb"
71+
}
72+
],
73+
"outputHashing": "all"
74+
},
75+
"development": {
76+
"optimization": false,
77+
"extractLicenses": false,
78+
"sourceMap": true
79+
}
80+
},
81+
"defaultConfiguration": "production"
82+
},
83+
"serve": {
84+
"builder": "@angular/build:dev-server",
85+
"configurations": {
86+
"production": {
87+
"buildTarget": "basic-persister:build:production"
88+
},
89+
"development": {
90+
"buildTarget": "basic-persister:build:development"
91+
}
92+
},
93+
"defaultConfiguration": "development"
94+
},
95+
"extract-i18n": {
96+
"builder": "@angular/build:extract-i18n",
97+
"options": {
98+
"buildTarget": "basic-persister:build"
99+
}
100+
}
101+
}
102+
}
103+
}
104+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@tanstack/query-example-angular-basic-persister",
3+
"type": "module",
4+
"scripts": {
5+
"ng": "ng",
6+
"start": "ng serve",
7+
"build": "ng build",
8+
"watch": "ng build --watch --configuration development"
9+
},
10+
"private": true,
11+
"dependencies": {
12+
"@angular/common": "^19.2.4",
13+
"@angular/compiler": "^19.2.4",
14+
"@angular/core": "^19.2.4",
15+
"@angular/platform-browser": "^19.2.4",
16+
"@angular/platform-browser-dynamic": "^19.2.4",
17+
"@tanstack/angular-query-experimental": "^5.73.3",
18+
"@tanstack/angular-query-persist-client": "^5.62.7",
19+
"@tanstack/query-sync-storage-persister": "^5.73.3",
20+
"rxjs": "^7.8.2",
21+
"tslib": "^2.8.1",
22+
"zone.js": "^0.15.0"
23+
},
24+
"devDependencies": {
25+
"@angular/build": "^19.2.5",
26+
"@angular/cli": "^19.2.5",
27+
"@angular/compiler-cli": "^19.2.4",
28+
"typescript": "5.8.3"
29+
}
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<p>
2+
Try to mock offline behavior with the button in the devtools. You can navigate
3+
around as long as there is already data in the cache. You'll get a refetch as
4+
soon as you go "online" again.
5+
</p>
6+
@if (postId() > -1) {
7+
<post [postId]="postId()" (setPostId)="postId.set($event)"></post>
8+
} @else {
9+
<posts (setPostId)="postId.set($event)" />
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
2+
import { PostComponent } from './components/post.component'
3+
import { PostsComponent } from './components/posts.component'
4+
5+
@Component({
6+
changeDetection: ChangeDetectionStrategy.OnPush,
7+
selector: 'basic-example',
8+
templateUrl: './app.component.html',
9+
imports: [PostComponent, PostsComponent],
10+
})
11+
export class BasicExampleComponent {
12+
postId = signal(-1)
13+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { provideHttpClient, withFetch } from '@angular/common/http'
2+
import {
3+
QueryClient,
4+
provideTanStackQuery,
5+
withDevtools,
6+
} from '@tanstack/angular-query-experimental'
7+
import { withPersistQueryClient } from '@tanstack/angular-query-persist-client'
8+
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'
9+
import type { ApplicationConfig } from '@angular/core'
10+
11+
const localStoragePersister = createSyncStoragePersister({
12+
storage: window.localStorage,
13+
})
14+
15+
export const appConfig: ApplicationConfig = {
16+
providers: [
17+
provideHttpClient(withFetch()),
18+
provideTanStackQuery(
19+
new QueryClient({
20+
defaultOptions: {
21+
queries: {
22+
staleTime: 1000 * 60, // 1 minute
23+
gcTime: 1000 * 60 * 60 * 24, // 24 hours
24+
},
25+
},
26+
}),
27+
withDevtools(),
28+
withPersistQueryClient({
29+
persistOptions: {
30+
persister: localStoragePersister,
31+
},
32+
}),
33+
),
34+
],
35+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<div>
2+
<div>
3+
<a (click)="setPostId.emit(-1)" href="#"> Back </a>
4+
</div>
5+
@if (postQuery.isPending()) {
6+
Loading...
7+
} @else if (postQuery.isError()) {
8+
Error: {{ postQuery.error().message }}
9+
}
10+
@if (postQuery.data(); as post) {
11+
<h1>{{ post.title }}</h1>
12+
<div>
13+
<p>{{ post.body }}</p>
14+
</div>
15+
@if (postQuery.isFetching()) {
16+
Background Updating...
17+
}
18+
}
19+
</div>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
EventEmitter,
5+
Output,
6+
inject,
7+
input,
8+
} from '@angular/core'
9+
import { QueryClient, injectQuery } from '@tanstack/angular-query-experimental'
10+
import { fromEvent, lastValueFrom, takeUntil } from 'rxjs'
11+
import { PostsService } from '../services/posts-service'
12+
13+
@Component({
14+
changeDetection: ChangeDetectionStrategy.OnPush,
15+
selector: 'post',
16+
standalone: true,
17+
templateUrl: './post.component.html',
18+
})
19+
export class PostComponent {
20+
#postsService = inject(PostsService)
21+
22+
@Output() setPostId = new EventEmitter<number>()
23+
24+
postId = input(0)
25+
26+
postQuery = injectQuery(() => ({
27+
enabled: this.postId() > 0,
28+
queryKey: ['post', this.postId()],
29+
queryFn: async (context) => {
30+
// Cancels the request when component is destroyed before the request finishes
31+
const abort$ = fromEvent(context.signal, 'abort')
32+
return lastValueFrom(
33+
this.#postsService.postById$(this.postId()).pipe(takeUntil(abort$)),
34+
)
35+
},
36+
}))
37+
38+
queryClient = inject(QueryClient)
39+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<div>
2+
<h1>Posts</h1>
3+
@switch (postsQuery.status()) {
4+
@case ('pending') {
5+
Loading...
6+
}
7+
@case ('error') {
8+
Error: {{ postsQuery.error()?.message }}
9+
}
10+
@default {
11+
<div class="todo-container">
12+
@for (post of postsQuery.data(); track post.id) {
13+
<p>
14+
<!-- We can access the query data here to show bold links for-->
15+
<!-- ones that are cached-->
16+
<a
17+
href="#"
18+
(click)="setPostId.emit(post.id)"
19+
[style]="
20+
queryClient.getQueryData(['post', post.id])
21+
? {
22+
fontWeight: 'bold',
23+
color: 'green',
24+
}
25+
: {}
26+
"
27+
>{{ post.title }}</a
28+
>
29+
</p>
30+
}
31+
</div>
32+
}
33+
}
34+
<div>
35+
@if (postsQuery.isFetching()) {
36+
Background Updating...
37+
}
38+
</div>
39+
</div>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
Component,
4+
EventEmitter,
5+
Output,
6+
inject,
7+
} from '@angular/core'
8+
import { QueryClient, injectQuery } from '@tanstack/angular-query-experimental'
9+
import { lastValueFrom } from 'rxjs'
10+
import { PostsService } from '../services/posts-service'
11+
12+
@Component({
13+
changeDetection: ChangeDetectionStrategy.OnPush,
14+
selector: 'posts',
15+
standalone: true,
16+
templateUrl: './posts.component.html',
17+
})
18+
export class PostsComponent {
19+
queryClient = inject(QueryClient)
20+
#postsService = inject(PostsService)
21+
22+
@Output() setPostId = new EventEmitter<number>()
23+
24+
postsQuery = injectQuery(() => ({
25+
queryKey: ['posts'],
26+
queryFn: () => lastValueFrom(this.#postsService.allPosts$()),
27+
}))
28+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { HttpClient } from '@angular/common/http'
2+
import { Injectable, inject } from '@angular/core'
3+
4+
@Injectable({
5+
providedIn: 'root',
6+
})
7+
export class PostsService {
8+
#http = inject(HttpClient)
9+
10+
postById$ = (postId: number) =>
11+
this.#http.get<Post>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
12+
13+
allPosts$ = () =>
14+
this.#http.get<Array<Post>>('https://jsonplaceholder.typicode.com/posts')
15+
}
16+
17+
export interface Post {
18+
id: number
19+
title: string
20+
body: string
21+
}
14.7 KB
Binary file not shown.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>TanStack Query Angular basic persister example</title>
6+
<base href="/" />
7+
<meta name="viewport" content="width=device-width, initial-scale=1" />
8+
<link rel="icon" type="image/x-icon" href="favicon.ico" />
9+
</head>
10+
<body>
11+
<basic-example></basic-example>
12+
</body>
13+
</html>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { bootstrapApplication } from '@angular/platform-browser'
2+
import { appConfig } from './app/app.config'
3+
import { BasicExampleComponent } from './app/app.component'
4+
5+
bootstrapApplication(BasicExampleComponent, appConfig).catch((err) =>
6+
console.error(err),
7+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"outDir": "./out-tsc/app",
5+
"types": []
6+
},
7+
"files": ["src/main.ts"],
8+
"include": ["src/**/*.d.ts"]
9+
}

0 commit comments

Comments
 (0)