ngneat / query

🚀 Powerful asynchronous state management, server-state utilities and data fetching for Angular Applications

Home Page:https://ngneat.github.io/query

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow to pass observable as state keys

ApplY3D opened this issue · comments

Which @ngneat/query-* package(s) are relevant/releated to the feature request?

No response

Description

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$)
}

Proposed solution

Add way to pass observable as state key and get them inside http query.

Alternatives considered

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>
  )
}

Do you want to create a pull request?

No

Persisted Query section?

@NetanelBasal

Persisted Query section?

I think it's different. In this example https://github.com/ngneat/query/blob/main/packages/playground/src/app/pagination-page/pagination-page.component.ts you should subscribe on observable and pass it's value to persisted query, right?

But I'm talking about the fact that we do not need to subscribe, let the library do it for you.

It should be easier:

// transform this
page$.pipe(switchMap(page => useQuery(['page',page])))
// into this
useQuery(['page',page$])

@NetanelBasal Just out of curiosity, do you think this would be possible now? I think with if we used signals, or transform all option keys to a signal and use a more sophisticated equals function for it this could be doable. 🤔

Do we really need it? Calling the exposed setOptions should be enough, no?

@luii I've added an updateOptions in next branch. You can test it if you have a real use case. I don't use it in my application.