Skip to content
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

feat(Angular-query): add Angular auto-refetching example #8371

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8ddf6d2
feat(examples): add angular auto-refetching example
MatanShushan Nov 24, 2024
2ca8129
feat(examples): add angular auto-refetching example
MatanShushan Nov 28, 2024
75010de
Merge branch 'main' into feat/query-example-angular-auto-refetching
MatanShushan Nov 28, 2024
b2e0b67
feat(examples): update lock file
MatanShushan Nov 29, 2024
317839f
Merge branch 'feat/query-example-angular-auto-refetching' of github.c…
MatanShushan Nov 29, 2024
b183601
Update lock file
MatanShushan Nov 29, 2024
35cfce6
ci: apply automated fixes
autofix-ci[bot] Nov 29, 2024
8425ab1
Update examples/angular/auto-refetching/.devcontainer/devcontainer.json
MatanShushan Dec 2, 2024
e4b315d
Update examples/angular/auto-refetching/package.json
MatanShushan Dec 2, 2024
0efc2be
Update examples/angular/auto-refetching/src/index.html
MatanShushan Dec 2, 2024
704c1b0
Update examples/angular/auto-refetching/tsconfig.json
MatanShushan Dec 2, 2024
6cca573
Update examples/angular/auto-refetching/tsconfig.json
MatanShushan Dec 2, 2024
65364f4
Update examples/angular/auto-refetching/tsconfig.json
MatanShushan Dec 2, 2024
2c877b9
Update examples/angular/auto-refetching/src/app/app.config.ts
MatanShushan Dec 2, 2024
19ff012
Update MR change interceptor to function interceptor and update all t…
MatanShushan Dec 2, 2024
16670d6
Merge remote-tracking branch 'upstream/main' into feat/query-example-…
MatanShushan Dec 4, 2024
2dd9e75
Update Angular version
MatanShushan Dec 4, 2024
b8962bb
Update Angular version
MatanShushan Dec 4, 2024
3a3065a
ci: apply automated fixes
autofix-ci[bot] Dec 4, 2024
deffa1d
Add fetching indicator
arnoud-dv Dec 4, 2024
000fcae
add example to docs
arnoud-dv Dec 4, 2024
87f2e6b
fix build
arnoud-dv Dec 4, 2024
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
4 changes: 4 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,10 @@
"label": "Basic",
"to": "framework/angular/examples/basic"
},
{
"label": "Auto Refetching / Polling / Realtime",
"to": "framework/angular/examples/auto-refetching"
},
{
"label": "Pagination",
"to": "framework/angular/examples/pagination"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Node.js",
"image": "mcr.microsoft.com/devcontainers/javascript-node:22"
}
6 changes: 6 additions & 0 deletions examples/angular/auto-refetching/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @ts-check

/** @type {import('eslint').Linter.Config} */
const config = {}

module.exports = config
6 changes: 6 additions & 0 deletions examples/angular/auto-refetching/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# TanStack Query Angular auto-refetching example

To run this example:

- `npm install` or `yarn` or `pnpm i` or `bun i`
- `npm run start` or `yarn start` or `pnpm start` or `bun start`
104 changes: 104 additions & 0 deletions examples/angular/auto-refetching/angular.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "pnpm",
"analytics": false,
"cache": {
"enabled": false
}
},
"newProjectRoot": "projects",
"projects": {
"auto-refetching": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"inlineTemplate": true,
"inlineStyle": true,
"skipTests": true
},
"@schematics/angular:class": {
"skipTests": true
},
"@schematics/angular:directive": {
"skipTests": true
},
"@schematics/angular:guard": {
"skipTests": true
},
"@schematics/angular:interceptor": {
"skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
},
"@schematics/angular:resolver": {
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": "dist/auto-refetching",
"index": "src/index.html",
"browser": "src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [],
"scripts": []
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "auto-refetching:build:production"
},
"development": {
"buildTarget": "auto-refetching:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular/build:extract-i18n",
"options": {
"buildTarget": "auto-refetching:build"
}
}
}
}
}
}
28 changes: 28 additions & 0 deletions examples/angular/auto-refetching/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "@tanstack/query-example-angular-auto-refetching",
"type": "module",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development"
},
"private": true,
"dependencies": {
"@angular/common": "^19.1.0-next.0",
"@angular/compiler": "^19.1.0-next.0",
"@angular/core": "^19.1.0-next.0",
"@angular/platform-browser": "^19.1.0-next.0",
"@angular/platform-browser-dynamic": "^19.1.0-next.0",
"@tanstack/angular-query-experimental": "^5.62.2",
"rxjs": "^7.8.1",
"tslib": "^2.6.3",
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular/build": "^19.0.2",
"@angular/cli": "^19.0.2",
"@angular/compiler-cli": "^19.1.0-next.0",
"typescript": "5.7.2"
}
}
11 changes: 11 additions & 0 deletions examples/angular/auto-refetching/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { AutoRefetchingExampleComponent } from './components/auto-refetching.component'

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-root',
standalone: true,
template: `<auto-refetching-example />`,
imports: [AutoRefetchingExampleComponent],
})
export class AppComponent {}
28 changes: 28 additions & 0 deletions examples/angular/auto-refetching/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
provideHttpClient,
withFetch,
withInterceptors,
} from '@angular/common/http'
import {
QueryClient,
provideTanStackQuery,
withDevtools,
} from '@tanstack/angular-query-experimental'
import { mockInterceptor } from './interceptor/mock-api.interceptor'
import type { ApplicationConfig } from '@angular/core'

export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(withFetch(), withInterceptors([mockInterceptor])),
provideTanStackQuery(
new QueryClient({
defaultOptions: {
queries: {
gcTime: 1000 * 60 * 60 * 24, // 24 hours
},
},
}),
withDevtools(),
),
],
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<div>
<h1>Auto Refetch with stale-time set to {{ intervalMs() }}ms</h1>
<p>
This example is best experienced on your own machine, where you can open
multiple tabs to the same localhost server and see your changes propagate
between the two.
</p>
<label>
Query Interval speed (ms):
<input [value]="intervalMs()" (input)="inputChange($event)" />
<span
[ngStyle]="{
display: 'inline-block',
marginLeft: '.5rem',
width: '10px',
height: '10px',
background: tasks.isFetching() ? 'green' : 'transparent',
transition: !tasks.isFetching() ? 'all .3s ease' : 'none',
borderRadius: '100%',
transform: 'scale(2)',
}"
></span>
</label>
<h2>Todo List</h2>

<input placeholder="Enter something" (keydown.enter)="addItem($event)" />
<ul>
@for (item of tasks.data(); track item) {
<li>{{ item }}</li>
}
</ul>
<div>
<button (click)="clearTasks()">Clear All</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
ChangeDetectionStrategy,
Component,
inject,
signal,
} from '@angular/core'
import {
injectMutation,
injectQuery,
} from '@tanstack/angular-query-experimental'
import { NgStyle } from '@angular/common'
import { TasksService } from '../services/tasks.service'

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'auto-refetching-example',
standalone: true,
templateUrl: './auto-refetching.component.html',
imports: [NgStyle],
})
export class AutoRefetchingExampleComponent {
#tasksService = inject(TasksService)

intervalMs = signal(1000)

tasks = injectQuery(() => this.#tasksService.allTasks(this.intervalMs()))

addMutation = injectMutation(() => this.#tasksService.addTask())
clearMutation = injectMutation(() => this.#tasksService.clearAllTasks())

clearTasks() {
this.clearMutation.mutate()
}

inputChange($event: Event) {
const target = $event.target as HTMLInputElement
this.intervalMs.set(Number(target.value))
}

addItem($event: Event) {
const target = $event.target as HTMLInputElement
const value = target.value
this.addMutation.mutate(value)
target.value = ''
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* MockApiInterceptor is used to simulate API responses for `/api/tasks` endpoints.
* It handles the following operations:
* - GET: Fetches all tasks from localStorage.
* - POST: Adds a new task to localStorage.
* - DELETE: Clears all tasks from localStorage.
* Simulated responses include a delay to mimic network latency.
*/
import { HttpResponse } from '@angular/common/http'
import { delay, of } from 'rxjs'
import type {
HttpEvent,
HttpHandlerFn,
HttpInterceptorFn,
HttpRequest,
} from '@angular/common/http'
import type { Observable } from 'rxjs'

export const mockInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn,
): Observable<HttpEvent<any>> => {
const respondWith = (status: number, body: any) =>
of(new HttpResponse({ status, body })).pipe(delay(100))
if (req.url === '/api/tasks') {
switch (req.method) {
case 'GET':
return respondWith(
200,
JSON.parse(localStorage.getItem('tasks') || '[]'),
)
case 'POST':
const tasks = JSON.parse(localStorage.getItem('tasks') || '[]')
tasks.push(req.body)
localStorage.setItem('tasks', JSON.stringify(tasks))
return respondWith(201, {
status: 'success',
task: req.body,
})
case 'DELETE':
localStorage.removeItem('tasks')
return respondWith(200, { status: 'success' })
}
}
return next(req)
}
59 changes: 59 additions & 0 deletions examples/angular/auto-refetching/src/app/services/tasks.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { HttpClient } from '@angular/common/http'
import { Injectable, inject } from '@angular/core'
import {
QueryClient,
mutationOptions,
queryOptions,
} from '@tanstack/angular-query-experimental'

import { lastValueFrom } from 'rxjs'

@Injectable({
providedIn: 'root',
})
export class TasksService {
#queryClient = inject(QueryClient) // Manages query state and caching
#http = inject(HttpClient) // Handles HTTP requests

/**
* Fetches all tasks from the API.
* Returns an observable containing an array of task strings.
*/
allTasks = (intervalMs: number) =>
queryOptions({
queryKey: ['tasks'],
queryFn: () => {
return lastValueFrom(this.#http.get<Array<string>>('/api/tasks'))
},
refetchInterval: intervalMs,
})

/**
* Creates a mutation for adding a task.
* On success, invalidates and refetches the "tasks" query cache to update the task list.
*/
addTask() {
return mutationOptions({
mutationFn: (task: string) =>
lastValueFrom(this.#http.post('/api/tasks', task)),
mutationKey: ['tasks'],
onSuccess: () => {
this.#queryClient.invalidateQueries({ queryKey: ['tasks'] })
},
})
}

/**
* Creates a mutation for clearing all tasks.
* On success, invalidates and refetches the "tasks" query cache to ensure consistency.
*/
clearAllTasks() {
return mutationOptions({
mutationFn: () => lastValueFrom(this.#http.delete('/api/tasks')),
mutationKey: ['clearTasks'],
onSuccess: () => {
this.#queryClient.invalidateQueries({ queryKey: ['tasks'] })
},
})
}
}
Binary file added examples/angular/auto-refetching/src/favicon.ico
Binary file not shown.
Loading
Loading