ngneat / query Goto Github PK
View Code? Open in Web Editor NEW๐ Powerful asynchronous state management, server-state utilities and data fetching for Angular Applications
Home Page: https://ngneat.github.io/query
๐ Powerful asynchronous state management, server-state utilities and data fetching for Angular Applications
Home Page: https://ngneat.github.io/query
query
No
I think it would wise to change the naming of the QueryClient
InjectionToken to QueryClientProvider
.
This way we can avoid the naming conflicts with the QueryClient
in @tanstack/query-core
package.
For now im importing both QueryClient
types from @ngneat/query
and @tanstack/query-core
and rename the first one to get full verbose typing in my code
import {QueryClient} from "@tanstack/query-core";
import {QueryClient as QueryClientProvider} from '@ngneat/query'
class A {
private readonly _queryClient: QueryClient = inject(QueryClientProvider)
}
I think this also makes sense since the QueryClient exported by @ngneat/query
is a Injection Token and not an instantiatable Class.
No response
No response
No response
No response
No
Cypress
query
Example of using @ngneat/query with StarWars API.
Something simmilar to this (https://tanstack.com/query/v4/docs/examples/react/star-wars).
None!
Yes
query
When creating and subscribing to the same query result multiple times (same key and query function) and unsubscribing one of the results (e.g. in a modal/dialog that gets destroyed after a while), it will cause a bug that prevents you from invalidating the query. If you try to invalidate (e.g. using invalidateQueries
) the query, it will get stuck in "fetching" status forever.
invalidateQueries
I guess this is happening because of line 76 in base-query.ts:
(mergedOptions as any)['queryFn']?.[SUBSCRIPTION]?.unsubscribe();
Because the second query result gets unsubscribed it will unsubscribe sourceSubscription
(utils.ts). Next time you try to invalidate, it will use the query function (queryFn$
) to create new subscription and assigning it to the unsubscribed sourceSubscription
. This will cause the originalQueryFn
function to immediately get unsubscribed and the Promise that is returned will never resolve. Therefore the query is stuck forever.
Maybe it would be enough to recreate the sourceSubscription
every time the query function is executed.
No response
No response
No response
No response
No
query
The following right now is not possible:
@Injectable({ providedIn: 'root' })
export class TodosService {
constructor(
private http: HttpClient,
private queryClient: QueryClient,
private useQuery: QueryProvider,
private useMutation: MutationProvider
) {}
}
cause of the error:
No suitable injection token for parameter 'queryClient' of class 'TodosService'.
Consider using the @Inject decorator to specify an injection token.
The following also requires types:
@Injectable({ providedIn: 'root' })
export class TodosService {
constructor(
private http: HttpClient,
@Inject(QueryClient) private queryClient: QueryClient,
@Inject(QueryProvider) private useQuery: QueryProvider,
@Inject(MutationProvider) private useMutation: MutationProvider
) {}
}
and fails with this error:
'QueryClient' refers to a value, but is being used as a type here. Did you mean 'typeof QueryClient'?ts(2749)
proposed typeof QueryClient
doesn't work too cause of
Property 'invalidateQueries' does not exist on type 'InjectionToken<QueryClient>'.ts(2339)
Not sure if there is a simple solution
The only solution that works for now is:
@Injectable({ providedIn: 'root' })
export class TodosService {
private http = inject(HttpClient);
private queryClient = inject(QueryClient);
private useQuery = inject(QueryProvider);
private useMutation = inject(MutationProvider);
}
No
todos$ = this.todosService.getTodos().pipe(filterSuccess()),
todos$ = this.todosService.getTodos().pipe(filterError())
todos$ = this.todosService.getTodos().pipe(select(result => result.data.foo))
query
No
I couldn't understand how to piece together the SSR snipets.
it seems outdated.
Help?
if it helps here is my server.ts code.
import 'zone.js/dist/zone-node';
import { APP_BASE_HREF } from '@angular/common';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import * as compression from 'compression';
import { existsSync } from 'fs';
import { join } from 'path';
import bootstrap from './src/main.server';
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
server.use(compression());
const distFolder = join(process.cwd(), 'dist/apps/nx-ng-test-project/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? 'index.original.html'
: 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
server.engine(
'html',
ngExpressEngine({
bootstrap,
})
);
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get(
'*.*',
express.static(distFolder, {
maxAge: '1y',
})
);
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
});
});
return server;
}
function run(): void {
const port = process.env['PORT'] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export default bootstrap;
No response
./node_modules/xhr2/lib/xhr2.js:281
throw new NetworkError(`Unsupported protocol ${this._url.protocol}`);
^
Error
at send (./node_modules/xhr2/lib/xhr2.js:281:19)
at Observable._subscribe (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:73345:13)
at Observable._trySubscribe (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:29130:19)
at /Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:29124:115
at Object.errorContext (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:38800:5)
at Observable.subscribe (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:29120:20)
at /Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:35662:57
at OperatorSubscriber._this._next (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:31916:9)
at OperatorSubscriber.Subscriber.next (/Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:29628:12)
at /Users/omerprizner/dev/nx-projects/dist/apps/nx-ng-test-project/server/vendor.js:31255:20
### Please provide the environment you discovered this bug in
```true
NX + Angular universal
Help me <3
Yes
query
Yes
In my module I have:
providers: [
provideQueryClientOptions({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
}),
],
Then I call useQuery
in a component, that declared in this module:
โฆ
this.useQuery(
['todo', id],
() => this.http.getTodo({ id })
).result$
โฆ
After switching between tabs new requests are called.
However, if I add refetchOnWindowFocus
to useQuery
itself, it works fine:
โฆ
this.useQuery(
['todo', id],
() => this.http.getTodo({ id }),
{ refetchOnWindowFocus: false }
).result$
โฆ
No response
No response
Angular 15.1.3
No response
No
The mapResultsData
Operator is not documented well enough yet and should be added to the README.md
and an example on how its used in parallel queries should be added to the playground.
query
No
I noticed that the result$
observable loses its typings and defaults to unkown in the template.
I tested this with my project and cloned the @ngneat/query
repository and also checked it there.
The typing it gets as soon it hits the template with async
Note: I used async
here because using @ngneat/subscribe
somehow doesnt unwrap the observable and gives me errors
Im not entirely sure what causes this but my guess would be the async
pipe
with *subscribe
this will happen:
No response
No response
Tested in Webstorm 2022.3 Beta
Tested in Webstorm 2022.2.3 (Stable)
Visual Studio Code
Freshly cloned the `@ngneat/query` repository and install the dependencies with `npm install`
If the type is added explicitly there is no problem at all, only when omitting it.
So im not sure if its a problem with the angular/language-service
or this package
No
Don't known / other
Yes
Pressing the menu button does nothing.
No response
No response
No response
No response
No
No response
If you use Tanstack Query in React world (useQuery(['posts', id], ()=>{})
), state keys are being recalculated every id
change together with data refetch.
@ngneat/query does not support this reactive recalculation. We should get new query for every new id
:
import { UseQuery } from '@ngneat/query';
@Injectable({ providedIn: 'root' })
export class TodosService {
private http = inject(HttpClient);
private useQuery = inject(UseQuery);
getTodo(id: number) {
return this.useQuery(['todo', id], () => {
return this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
});
}
}
To provide reactive functionality, useQuery
function should consume object with observable values and automatically refetch data on observable changes:
import { UseQuery } from '@ngneat/query';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class TodosService {
private http = inject(HttpClient);
private useQuery = inject(UseQuery);
getTodoById(id$: Observable<number>) {
return this.useQuery(['todo', {id: $id}], ({ id }) => {
return this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
});
}
}
Then inside component:
@Component()
export class TodosPageComponent {
id$ = new BehaviorSubject(1) // signal(1)
todos$ = inject(TodosService).getTodoById(this.id$)
}
Add way to pass observable as state key and get them inside http query.
https://github.com/liaoliao666/react-query-kit
import { createQuery } from 'react-query-kit'
type Response = { title: string; content: string }
type Variables = { id: number }
const usePost = createQuery<Response, Variables, Error>({
primaryKey: '/posts',
queryFn: ({ queryKey: [primaryKey, variables] }) => {
// primaryKey equals to '/posts'
return fetch(`${primaryKey}/${variables.id}`).then(res => res.json())
},
suspense: true
})
const variables: Variables = { id: 1 }
export default function Page() {
// queryKey equals to ['/posts', { id: 1 }]
const { data } = usePost({ variables, suspense: true })
return (
<div>
<div>{data?.title}</div>
<div>{data?.content}</div>
</div>
)
}
No
No response
Implement Auto Refetching example in playground.
Similar to this example:
https://tanstack.com/query/v4/docs/react/examples/react/auto-refetching
None
No
query
Yes
The optimistic updates example in the demo doesn't work.
Clicking the button doesn't do anything.
https://ngneat.github.io/query/optimistic-updates
No response
No response
No response
No
Cypress
query
For any query or mutation you first need to inject a token and the use it, for example private useQuery = inject(UseQuery);
and this.useQuery(...)
. This could be optimized by exporting a function which includes both lines.
import { useQuery } from '@ngneat/query';
@Injectable({ providedIn: 'root' })
export class TodosService {
private http = inject(HttpClient);
getTodos() {
return useQuery(['todos'], () => {
return this.http.get<Todo[]>(
'https://jsonplaceholder.typicode.com/todos'
);
});
}
getTodo(id: number) {
return useQuery(['todo', id], () => {
return this.http.get<Todo>(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
});
}
}
No
query
No
When setting refetchOnWindowFocus: "always", I expect the query to refetch when I click outside, then inside the window. That is how it works for me in react. It doesn't work here for some reason.
import { HttpClient } from '@angular/common/http';
import { Component, inject } from '@angular/core';
import { UseQuery } from '@ngneat/query';
@Component({
selector: 'app-root',
template: `<div>
<div *ngIf="pokemon.result$ | async as pokemon">
<p *ngIf="pokemon.isError">Error...</p>
<div *ngIf="pokemon.data">
<div *ngFor="let pokemon of pokemon.data.results">
<div>{{ pokemon.name }}</div>
</div>
</div>
</div>
</div>`,
})
export class AppComponent {
private useQuery = inject(UseQuery);
private http = inject(HttpClient);
getPokemon() {
return this.useQuery(
['pokemon'],
() => {
return this.http.get<{
results: { name: string; url: string }[];
}>('https://pokeapi.co/api/v2/pokemon');
},
{
refetchOnWindowFocus: 'always',
}
);
}
pokemon = this.getPokemon();
}
No response
No response
No response
No response
No
query
Add Rick Morty's example as seen on
https://tanstack.com/query/v4/docs/examples/react/rick-morty
N/A
N/A
Yes
No response
Take one or more of the following examples, create an issue, and assign it to yourself:
No
query
https://tanstack.com/query/v4/docs/examples/react/default-query-function
Yes
query
Add prefetching example (https://tanstack.com/query/v4/docs/examples/react/prefetching) to our playground.
RickAndMortyService
to access https://rickandmortyapi.com/api
api.prefetching
capabilities under the /prefetching
route as show in https://tanstack.com/query/v4/docs/examples/react/prefetching .No.
Yes
Think if we really need to support this method as we can use a higher-order observable instead.
If yes, the reset observer method should return an observable instead of a promise.
fetchNextPage and fetchPreviousPage should return an observable
No response
combineLatest([
a$,
b$
]).pipe(
mapResultsData(([aData. bData]) => {
return mappedData
})
)
combineLatest([
a$,
b$
]).pipe(
mapResultsData(([aData. bData]) => {
return mappedData
})
)
No
No
query
I would like to use a basic async function as the queryFn.
For example this one:
export const getPets = async () => {
const response = await fetch('https://pokeapi.co/api/v2/pokemon');
const json: {
results: { name: string; url: string }[];
} = await response.json();
return json;
};
Add the extra types for all of the exported functions, so they will accept basic async functions where they can, instead of only supporting observables. Check if the queryFn returns a promise or observable. If it returns a promise, then use that/wrap it with from
.
Wrapping every promise in from
, but I would like cleaner code.
No
query
No
No response
No response
No response
No response
Yes
query
No
Hi, I'm long time react-query user who recently join an angular development team. Very excited to see your library to come.
However, I noticed that query won't refresh when new instances of the query mount when I build my todos app demo here.
I use *ngIf
to conditionally render todo list component. when the list component mounted again, the data in the cache showd in the component but no refetch is sending.
No response
No response
No response
No response
No
query
No
When trying to create a component and initially calling detectChanges
as would be usually done:
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({ declarations: [AppComponent] });
TestBed.compileComponents();
const component = TestBed.createComponent(AppComponent);
component.detectChanges();
}));
All tests in the file will fail due to jest timeout.
It also doesn't matter if I attempt to detect changes outside of the beforeEach
hook, because then only the first test will succeed, while all subsequent tests will fail.
https://github.com/cuddlecake/query/blob/main/packages/ng-query/src/lib/tests/component.spec.ts
No response
No response
My minimal reproduction is as minimal as it gets, but I first stumbled on this when I tried to introduce @ngneat/query
in my app with a proper use case, involving an HTTP Request.
No
query
No
Providing a custom QUERY_CLIENT_CONFIG
as currently done in the playground app
return <QueryClientConfig>{
defaultOptions: {
queries: {
queryFn,
},
},
};
will overwrite/remove the default configuration of staleTime: Infinity
in QueryClient
:
export const QueryClient = new InjectionToken<QueryCore>('QueryClient', {
providedIn: 'root',
factory() {
const options = inject(QUERY_CLIENT_OPTIONS);
return new QueryCore({
defaultOptions: {
queries: {
staleTime: Infinity,
...options?.defaultOptions?.queries,
},
},
...options,
});
},
});
The default configuration of staleTime
should only be overwritten, if a custom configuration explicitly provide a staleTime
.
`staleTime: Infinity` is not applied, hence the prefetching example https://github.com/ngneat/query/pull/40 does not work.
Discovered in https://github.com/ngneat/query/pull/40.
No response
Yes
I was thinking about creating a nice abstraction named Resource
, which encapsulates the key
and keep it DRY, and also exposes other helpful methods to deal with entities. Here is my initial sketch:
class TodosService {
todosResource = new Resource('todos');
todoResource = new Resource<{ id: string }>(({ id }) => ['todo', id]);
getTodos() {
return this.todosResource.query(() => {
return this.http.get('')
})
}
addTodo(title: string) {
return this.todosResource.mutate<Params>((params) => {
return this.http.post('', params)
}).pipe(
tap(todo => {
this.todosResource.addEntity() // need to think about the signature
})
)
}
removeTodo(title: string) {
return this.todosResource.mutate<Params>((params) => {
return this.http.delete('', params)
}).pipe(
tap(todo => {
this.todosResource.removeEntity() // need to think about the signature
})
)
}
updateTodo(title: string) {
return this.todosResource.mutate<Params>((params) => {
return this.http.put('', params)
}).pipe(
tap(todo => {
this.todosResource.updateEntity() // need to think about the signature
this.todosResource.invalidate()
})
)
}
getTodo(id: string) {
return this.todoResource.query({ id }, () => {
return this.http.get(id);
})
}
}
Hi. I was wondering how you can use the persistQueryClient functionality with this?
No response
Change addEntity
to arrayPush
and support flat array. Create arrayRemove
, arrayUpdate
, etc.
Similiar API to https://opensource.salesforce.com/akita/docs/additional/array
None
No
Hi ๐๐ป,
thanks for working on this library. I really like the approach and the intuitive API.
Are there plans to support constructor
-Injection for QueryClientService?
Currently, it does not work since it is an InjectionToken
, which causes compilation errors (see images below).
Maybe QueryClient
needs to be exposed as Public API.
query
Yes
If I use provideQueryClientOptions
, and pass in an empty object, it changes the behaviour of the queries when it shouldn't.
Specifically, one of my queries is stuck in the "fetching" state
// app.module.ts
provideQueryClientOptions({
defaultOptions: {}
})
No response
My query is stuck in "fetching"
No response
No response
No
We need to check if we REALLY need to support additional overloads.
No response
Implements Optimistic exemple in playground
https://tanstack.com/query/v4/docs/examples/react/optimistic-updates-typescript)
None
No
I saw that when you execute useQuery currently it returns an object with a result$ property.
Did you think about the possibility to return an observable directly that you extend with some other properties?
Like here
Set up GitHub page for our playground.
Replace the local express app with a free online service for the API of the basic example with mutations.
query
No
Hi
To reproduce create a mutation using the service, subscribe to the result$, unsubscribe to it, resubscribe --> all I receive is only a loading result.
My use case is I want to be able to create the mutation in a service and use it in multiple component as I want but after the first unsubscribe I am no longer able to use the mutation.
BR
Florent
After a unsubscribe to a result$ mutation the mutation is not longer working and stay in a loading state (as if the observable is no longer subscribe) even if a valid subscription is still on.
No response
No response
Yes
No response
Add placeholder data example
https://tanstack.com/query/v4/docs/react/guides/placeholder-query-data
N/A
Yes
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.