Skip to content

Commit cc47417

Browse files
fix(angular-query): run mutation callback in injection context (#7360)
* fix(angular-query-experimental): run mutation callback in injection context Previously, the injectMutation callback was run in injection context only if accessed in the same task as the component initialization (i.e. before the `effect` callback run). By running the effect run in injection context, it is ensured that the callback will always run in injection context, and any `inject` calls will not fail. Note there is a separate issue here, and it's that the callback is always run twice - once synchronously when initialization the mutation, and secondly as the first callback of the `effect`. This is something that can potentially be improved. No breaking changes, any code that worked before should still work. * run prettier * Catch error in test --------- Co-authored-by: Arnoud <6420061+arnoud-dv@users.noreply.github.com>
1 parent 62e2620 commit cc47417

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

packages/angular-query-experimental/src/__tests__/inject-mutation.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, input, signal } from '@angular/core'
1+
import { Component, Injectable, inject, input, signal } from '@angular/core'
22
import { TestBed } from '@angular/core/testing'
33
import { describe, expect, vi } from 'vitest'
44
import { By } from '@angular/platform-browser'
@@ -453,4 +453,45 @@ describe('injectMutation', () => {
453453

454454
await expect(() => mutateAsync()).rejects.toThrowError(err)
455455
})
456+
457+
test('should execute callback in injection context', async () => {
458+
const errorSpy = vi.fn()
459+
@Injectable()
460+
class FakeService {
461+
updateData(name: string) {
462+
return Promise.resolve(name)
463+
}
464+
}
465+
466+
@Component({
467+
selector: 'app-fake',
468+
template: ``,
469+
standalone: true,
470+
providers: [FakeService],
471+
})
472+
class FakeComponent {
473+
mutation = injectMutation(() => {
474+
try {
475+
const service = inject(FakeService)
476+
return {
477+
mutationFn: (name: string) => service.updateData(name),
478+
}
479+
} catch (e) {
480+
errorSpy(e)
481+
throw e
482+
}
483+
})
484+
}
485+
486+
const fixture = TestBed.createComponent(FakeComponent)
487+
fixture.detectChanges()
488+
489+
// check if injection contexts persist in a different task
490+
await new Promise<void>((resolve) => queueMicrotask(() => resolve()))
491+
492+
expect(
493+
await fixture.componentInstance.mutation.mutateAsync('test'),
494+
).toEqual('test')
495+
expect(errorSpy).not.toHaveBeenCalled()
496+
})
456497
})

packages/angular-query-experimental/src/inject-mutation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ export function injectMutation<
7070
}
7171

7272
effect(() => {
73-
observer.setOptions(optionsFn(queryClient))
73+
observer.setOptions(
74+
runInInjectionContext(currentInjector, () =>
75+
optionsFn(queryClient),
76+
),
77+
)
7478
})
7579

7680
const result = signal(observer.getCurrentResult())

0 commit comments

Comments
 (0)