diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts
index ad29c381bb..db1f7aeea1 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-is-fetching.test.ts
@@ -60,6 +60,36 @@ describe('injectIsFetching', () => {
expect(rendered.getByText('fetching: 0')).toBeInTheDocument()
})
+ it('should be able to filter by queryKey', async () => {
+ const key1 = queryKey()
+ const key2 = queryKey()
+
+ @Component({
+ template: `
fetching: {{ isFetching() }}
`,
+ })
+ class Page {
+ readonly query1 = injectQuery(() => ({
+ queryKey: key1,
+ queryFn: () => sleep(10).then(() => 'test1'),
+ }))
+ readonly query2 = injectQuery(() => ({
+ queryKey: key2,
+ queryFn: () => sleep(100).then(() => 'test2'),
+ }))
+ readonly isFetching = injectIsFetching({ queryKey: key1 })
+ }
+
+ const rendered = await render(Page)
+
+ await vi.advanceTimersByTimeAsync(0)
+ rendered.fixture.detectChanges()
+ expect(rendered.getByText('fetching: 1')).toBeInTheDocument()
+
+ await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
+ expect(rendered.getByText('fetching: 0')).toBeInTheDocument()
+ })
+
describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
expect(() => {
diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts
index 91d3e89afb..66fe678d03 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-is-mutating.test.ts
@@ -62,6 +62,39 @@ describe('injectIsMutating', () => {
expect(rendered.getByText('mutating: 0')).toBeInTheDocument()
})
+ it('should be able to filter by mutationKey', async () => {
+ const key1 = queryKey()
+ const key2 = queryKey()
+
+ @Component({
+ template: `mutating: {{ isMutating() }}
`,
+ })
+ class Page {
+ readonly mutation1 = injectMutation(() => ({
+ mutationKey: key1,
+ mutationFn: () => sleep(10).then(() => 'data1'),
+ }))
+ readonly mutation2 = injectMutation(() => ({
+ mutationKey: key2,
+ mutationFn: () => sleep(100).then(() => 'data2'),
+ }))
+ readonly isMutating = injectIsMutating({ mutationKey: key1 })
+ }
+
+ const rendered = await render(Page)
+
+ rendered.fixture.componentInstance.mutation1.mutate()
+ rendered.fixture.componentInstance.mutation2.mutate()
+
+ await vi.advanceTimersByTimeAsync(0)
+ rendered.fixture.detectChanges()
+ expect(rendered.getByText('mutating: 1')).toBeInTheDocument()
+
+ await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
+ expect(rendered.getByText('mutating: 0')).toBeInTheDocument()
+ })
+
describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
expect(() => {
diff --git a/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts b/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts
index 0b74019b3a..73659d74a5 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-is-restoring.test.ts
@@ -43,6 +43,26 @@ describe('injectIsRestoring', () => {
expect(isRestoring()).toBe(true)
})
+ it('should reactively reflect changes to the provided signal', () => {
+ const restoringSignal = signal(true)
+
+ TestBed.configureTestingModule({
+ providers: [provideIsRestoring(restoringSignal.asReadonly())],
+ })
+
+ const isRestoring = TestBed.runInInjectionContext(() => {
+ return injectIsRestoring()
+ })
+
+ expect(isRestoring()).toBe(true)
+
+ restoringSignal.set(false)
+ expect(isRestoring()).toBe(false)
+
+ restoringSignal.set(true)
+ expect(isRestoring()).toBe(true)
+ })
+
it('should be usable outside injection context when passing an injector', () => {
const isRestoring = injectIsRestoring({
injector: TestBed.inject(Injector),
diff --git a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts
index b9d3eea6e1..f0c53602b1 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts
@@ -50,47 +50,67 @@ describe('injectMutation', () => {
it('should change state after invoking mutate', async () => {
const result = 'Mock data'
- const mutation = TestBed.runInInjectionContext(() => {
- return injectMutation(() => ({
+ @Component({
+ template: `
+ isIdle: {{ mutation.isIdle() }}
+ isPending: {{ mutation.isPending() }}
+ isError: {{ mutation.isError() }}
+ isSuccess: {{ mutation.isSuccess() }}
+ data: {{ mutation.data() ?? 'none' }}
+ error: {{ mutation.error()?.message ?? 'none' }}
+ `,
+ })
+ class Page {
+ readonly mutation = injectMutation(() => ({
mutationFn: (params: string) => sleep(10).then(() => params),
}))
- })
+ }
- TestBed.tick()
+ const rendered = await render(Page)
- mutation.mutate(result)
+ rendered.fixture.componentInstance.mutation.mutate(result)
await vi.advanceTimersByTimeAsync(0)
+ rendered.fixture.detectChanges()
- expectSignals(mutation, {
- isIdle: false,
- isPending: true,
- isError: false,
- isSuccess: false,
- data: undefined,
- error: null,
- })
+ expect(rendered.getByText('isIdle: false')).toBeInTheDocument()
+ expect(rendered.getByText('isPending: true')).toBeInTheDocument()
+ expect(rendered.getByText('isError: false')).toBeInTheDocument()
+ expect(rendered.getByText('isSuccess: false')).toBeInTheDocument()
+ expect(rendered.getByText('data: none')).toBeInTheDocument()
+ expect(rendered.getByText('error: none')).toBeInTheDocument()
})
it('should return error when request fails', async () => {
- const mutation = TestBed.runInInjectionContext(() => {
- return injectMutation(() => ({
+ @Component({
+ template: `
+ isIdle: {{ mutation.isIdle() }}
+ isPending: {{ mutation.isPending() }}
+ isError: {{ mutation.isError() }}
+ isSuccess: {{ mutation.isSuccess() }}
+ data: {{ mutation.data() ?? 'none' }}
+ error: {{ mutation.error()?.message ?? 'none' }}
+ `,
+ })
+ class Page {
+ readonly mutation = injectMutation(() => ({
mutationFn: () =>
sleep(10).then(() => Promise.reject(new Error('Some error'))),
}))
- })
+ }
- mutation.mutate()
+ const rendered = await render(Page)
+
+ rendered.fixture.componentInstance.mutation.mutate()
await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
- expectSignals(mutation, {
- isIdle: false,
- isPending: false,
- isError: true,
- isSuccess: false,
- data: undefined,
- error: Error('Some error'),
- })
+ expect(rendered.getByText('isIdle: false')).toBeInTheDocument()
+ expect(rendered.getByText('isPending: false')).toBeInTheDocument()
+ expect(rendered.getByText('isError: true')).toBeInTheDocument()
+ expect(rendered.getByText('isSuccess: false')).toBeInTheDocument()
+ expect(rendered.getByText('data: none')).toBeInTheDocument()
+ expect(rendered.getByText('error: Some error')).toBeInTheDocument()
})
it('should return data when request succeeds', async () => {
@@ -498,6 +518,21 @@ describe('injectMutation', () => {
await expect(() => mutateAsync()).rejects.toThrow(err)
})
+ it('should resolve mutateAsync with the value returned from mutationFn', async () => {
+ const key = queryKey()
+ const { mutateAsync } = TestBed.runInInjectionContext(() => {
+ return injectMutation(() => ({
+ mutationKey: key,
+ mutationFn: (params: string) => sleep(10).then(() => params),
+ }))
+ })
+
+ const promise = mutateAsync('Mock data')
+ await vi.advanceTimersByTimeAsync(11)
+
+ await expect(promise).resolves.toBe('Mock data')
+ })
+
describe('injection context', () => {
it('should throw NG0203 with descriptive error outside injection context', () => {
const key = queryKey()
diff --git a/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts
index 2d3560b99b..523bda69f0 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-queries.test.ts
@@ -136,6 +136,50 @@ describe('injectQueries', () => {
expect(results.length).toBeGreaterThanOrEqual(2)
})
+ it('should reflect error state when one of the queries rejects', async () => {
+ const key1 = queryKey()
+ const key2 = queryKey()
+
+ @Component({
+ template: `
+
+ status1: {{ result()[0].status() }}, error1:
+ {{ result()[0].error()?.message ?? 'none' }}
+
+
+ status2: {{ result()[1].status() }}, data2:
+ {{ result()[1].data() ?? 'none' }}
+
+ `,
+ })
+ class Page {
+ readonly result = injectQueries(() => ({
+ queries: [
+ {
+ queryKey: key1,
+ queryFn: () =>
+ sleep(10).then(() => Promise.reject(new Error('Some error'))),
+ retry: false,
+ },
+ {
+ queryKey: key2,
+ queryFn: () => sleep(10).then(() => 2),
+ },
+ ],
+ }))
+ }
+
+ const rendered = await render(Page)
+
+ await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
+
+ expect(
+ rendered.getByText('status1: error, error1: Some error'),
+ ).toBeInTheDocument()
+ expect(rendered.getByText('status2: success, data2: 2')).toBeInTheDocument()
+ })
+
describe('isRestoring', () => {
it('should not fetch for the duration of the restoring period when isRestoring is true', async () => {
const key1 = queryKey()
diff --git a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts
index 82eb8f0f34..a8bff8527b 100644
--- a/packages/angular-query-experimental/src/__tests__/inject-query.test.ts
+++ b/packages/angular-query-experimental/src/__tests__/inject-query.test.ts
@@ -356,6 +356,62 @@ describe('injectQuery', () => {
expect(rendered.getByText('failureReason: Some error')).toBeInTheDocument()
})
+ it('should be able to select a part of the data with select', async () => {
+ const key = queryKey()
+
+ @Component({
+ template: `data: {{ query.data() ?? 'none' }}
`,
+ })
+ class Page {
+ readonly query = injectQuery<{ name: string }, Error, string>(() => ({
+ queryKey: key,
+ queryFn: () => sleep(10).then(() => ({ name: 'test' })),
+ select: (data) => data.name,
+ }))
+ }
+
+ const rendered = await render(Page)
+
+ expect(rendered.getByText('data: none')).toBeInTheDocument()
+
+ await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
+
+ expect(rendered.getByText('data: test')).toBeInTheDocument()
+ })
+
+ it('should show placeholderData until queryFn resolves and then expose real data', async () => {
+ const key = queryKey()
+
+ @Component({
+ template: `
+ data: {{ query.data() }}
+ isPlaceholderData: {{ query.isPlaceholderData() }}
+ isSuccess: {{ query.isSuccess() }}
+ `,
+ })
+ class Page {
+ readonly query = injectQuery(() => ({
+ queryKey: key,
+ queryFn: () => sleep(10).then(() => 'real-data'),
+ placeholderData: 'placeholder',
+ }))
+ }
+
+ const rendered = await render(Page)
+
+ expect(rendered.getByText('data: placeholder')).toBeInTheDocument()
+ expect(rendered.getByText('isPlaceholderData: true')).toBeInTheDocument()
+ expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
+
+ await vi.advanceTimersByTimeAsync(11)
+ rendered.fixture.detectChanges()
+
+ expect(rendered.getByText('data: real-data')).toBeInTheDocument()
+ expect(rendered.getByText('isPlaceholderData: false')).toBeInTheDocument()
+ expect(rendered.getByText('isSuccess: true')).toBeInTheDocument()
+ })
+
it('should update query on options contained signal change', async () => {
const key1 = queryKey()
const key2 = queryKey()
@@ -548,24 +604,6 @@ describe('injectQuery', () => {
})
})
- it('should set state to error when queryFn returns reject promise', async () => {
- const key = queryKey()
- const query = TestBed.runInInjectionContext(() => {
- return injectQuery(() => ({
- retry: false,
- queryKey: key,
- queryFn: () =>
- sleep(10).then(() => Promise.reject(new Error('Some error'))),
- }))
- })
-
- expect(query.status()).toBe('pending')
-
- await vi.advanceTimersByTimeAsync(11)
-
- expect(query.status()).toBe('error')
- })
-
it('should render with required signal inputs', async () => {
@Component({
selector: 'app-fake',