Skip to content
Merged
Show file tree
Hide file tree
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: 1 addition & 1 deletion docs/framework/angular/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ We are in the process of getting to a stable API for Angular Query. If you have

## Supported Angular Versions

Angular Query is compatible with Angular version v16 and higher.
Angular Query is compatible with Angular v16 and higher.

TanStack Query (FKA React Query) is often described as the missing data-fetching library for web applications, but in more technical terms, it makes **fetching, caching, synchronizing and updating server state** in your web applications a breeze.

Expand Down
1 change: 1 addition & 0 deletions examples/angular/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"zone.js": "^0.14.3"
},
"devDependencies": {
"@nitedani/vite-plugin-angular": "^17.0.11",
"typescript": "5.2.2",
"vite": "^5.0.10"
}
Expand Down
139 changes: 3 additions & 136 deletions examples/angular/basic/src/basic-example.component.ts
Original file line number Diff line number Diff line change
@@ -1,140 +1,7 @@
import { AngularQueryDevtools } from '@tanstack/angular-query-devtools-experimental'
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
inject,
signal,
} from '@angular/core'
import {
injectQuery,
injectQueryClient,
} from '@tanstack/angular-query-experimental'
import { HttpClient } from '@angular/common/http'
import { fromEvent, lastValueFrom, takeUntil } from 'rxjs'

type Post = {
id: number
title: string
body: string
}

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'post',
standalone: true,
template: `
<div>
<div>
<a (click)="setPostId.emit(-1)" href="#"> Back </a>
</div>
@if (postQuery.status() === 'pending') {
Loading...
} @else if (postQuery.status() === 'error') {
Error: {{ postQuery.error()?.message }}
}
@if (postQuery.data(); as post) {
<h1>{{ post.title }}</h1>
<div>
<p>{{ post.body }}</p>
</div>
@if (postQuery.isFetching()) {
Background Updating...
}
}
</div>
`,
})
export class PostComponent {
@Output() setPostId = new EventEmitter<number>()

// Until Angular supports signal-based inputs, we have to set a signal
@Input({ required: true, alias: 'postId' })
set _postId(value: number) {
this.postId.set(value)
}
postId = signal(0)
httpClient = inject(HttpClient)

getPost$ = (postId: number) => {
return this.httpClient.get<Post>(
`https://jsonplaceholder.typicode.com/posts/${postId}`,
)
}

postQuery = injectQuery(() => ({
enabled: this.postId() > 0,
queryKey: ['post', this.postId()],
queryFn: async (context): Promise<Post> => {
// Cancels the request when component is destroyed before the request finishes
const abort$ = fromEvent(context.signal, 'abort')
return lastValueFrom(this.getPost$(this.postId()).pipe(takeUntil(abort$)))
},
}))

queryClient = injectQueryClient()
}

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'posts',
standalone: true,
template: `<div>
<h1>Posts</h1>
@switch (postsQuery.status()) {
@case ('pending') {
Loading...
}
@case ('error') {
Error: {{ postsQuery.error()?.message }}
}
@default {
<div class="todo-container">
@for (post of postsQuery.data(); track post.id) {
<p>
<!-- We can access the query data here to show bold links for-->
<!-- ones that are cached-->
<a
href="#"
(click)="setPostId.emit(post.id)"
[style]="
queryClient.getQueryData(['post', post.id])
? {
fontWeight: 'bold',
color: 'green'
}
: {}
"
>{{ post.title }}</a
>
</p>
}
</div>
}
}
<div>
@if (postsQuery.isFetching()) {
Background Updating...
}
</div>
</div> `,
})
export class PostsComponent {
@Output() setPostId = new EventEmitter<number>()

posts$ = inject(HttpClient).get<Array<Post>>(
'https://jsonplaceholder.typicode.com/posts',
)

postsQuery = injectQuery(() => ({
queryKey: ['posts'],
queryFn: () => lastValueFrom(this.posts$),
}))

queryClient = injectQueryClient()
}
import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
import { PostComponent } from './post.component'
import { PostsComponent } from './posts.component'

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
Expand Down
19 changes: 19 additions & 0 deletions examples/angular/basic/src/post.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<div>
<div>
<a (click)="setPostId.emit(-1)" href="#"> Back </a>
</div>
@if (postQuery.status() === 'pending') {
Loading...
} @else if (postQuery.status() === 'error') {
Error: {{ postQuery.error()?.message }}
}
@if (postQuery.data(); as post) {
<h1>{{ post.title }}</h1>
<div>
<p>{{ post.body }}</p>
</div>
@if (postQuery.isFetching()) {
Background Updating...
}
}
</div>
52 changes: 52 additions & 0 deletions examples/angular/basic/src/post.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { HttpClient } from '@angular/common/http'
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
inject,
signal,
} from '@angular/core'
import {
injectQuery,
injectQueryClient,
} from '@tanstack/angular-query-experimental'
import { fromEvent, lastValueFrom, takeUntil } from 'rxjs'
import { PostsService } from './posts-service'

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'post',
standalone: true,
templateUrl: './post.component.html',
})
export class PostComponent {
#postsService = inject(PostsService)

@Output() setPostId = new EventEmitter<number>()

// This can be replaced with an input signal in Angular v17.1+:
// postId = input(0)
@Input({ required: true, alias: 'postId' })
set _postId(value: number) {
this.postId.set(value)
}
postId = signal(0)

httpClient = inject(HttpClient)

postQuery = injectQuery(() => ({
enabled: this.postId() > 0,
queryKey: ['post', this.postId()],
queryFn: async (context) => {
// Cancels the request when component is destroyed before the request finishes
const abort$ = fromEvent(context.signal, 'abort')
return lastValueFrom(
this.#postsService.postById$(this.postId()).pipe(takeUntil(abort$)),
)
},
}))

queryClient = injectQueryClient()
}
21 changes: 21 additions & 0 deletions examples/angular/basic/src/posts-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { HttpClient } from '@angular/common/http'
import { Injectable, inject } from '@angular/core'

@Injectable({
providedIn: 'root',
})
export class PostsService {
#http = inject(HttpClient)

postById$ = (postId: number) =>
this.#http.get<Post>(`https://jsonplaceholder.typicode.com/posts/${postId}`)

allPosts$ = () =>
this.#http.get<Array<Post>>('https://jsonplaceholder.typicode.com/posts')
}

export interface Post {
id: number
title: string
body: string
}
39 changes: 39 additions & 0 deletions examples/angular/basic/src/posts.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<div>
<h1>Posts</h1>
@switch (postsQuery.status()) {
@case ('pending') {
Loading...
}
@case ('error') {
Error: {{ postsQuery.error()?.message }}
}
@default {
<div class="todo-container">
@for (post of postsQuery.data(); track post.id) {
<p>
<!-- We can access the query data here to show bold links for-->
<!-- ones that are cached-->
<a
href="#"
(click)="setPostId.emit(post.id)"
[style]="
queryClient.getQueryData(['post', post.id])
? {
fontWeight: 'bold',
color: 'green'
}
: {}
"
>{{ post.title }}</a
>
</p>
}
</div>
}
}
<div>
@if (postsQuery.isFetching()) {
Background Updating...
}
</div>
</div>
32 changes: 32 additions & 0 deletions examples/angular/basic/src/posts.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Output,
inject,
} from '@angular/core'
import {
injectQuery,
injectQueryClient,
} from '@tanstack/angular-query-experimental'
import { lastValueFrom } from 'rxjs'
import { PostsService } from './posts-service'

@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'posts',
standalone: true,
templateUrl: './posts.component.html',
})
export class PostsComponent {
#postsService = inject(PostsService)

@Output() setPostId = new EventEmitter<number>()

postsQuery = injectQuery(() => ({
queryKey: ['posts'],
queryFn: () => lastValueFrom(this.#postsService.allPosts$()),
}))

queryClient = injectQueryClient()
}
10 changes: 10 additions & 0 deletions examples/angular/basic/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { angular } from '@nitedani/vite-plugin-angular/plugin'
import { defineConfig } from 'vite'

export default defineConfig({
resolve: {
mainFields: ['module'],
},

plugins: [angular()],
})
2 changes: 1 addition & 1 deletion packages/angular-query-experimental/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Visit https://tanstack.com/query/latest/docs/angular/overview

# Quick Start

> Angular Query requires Angular 17.
> Angular Query requires Angular 16.

1. Install `angular-query`

Expand Down
Loading