Skip to content

Commit

Permalink
fix(angular-query): run mutation callback in injection context (#7360)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
ShacharHarshuv and arnoud-dv authored Jun 21, 2024
1 parent 62e2620 commit cc47417
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, input, signal } from '@angular/core'
import { Component, Injectable, inject, input, signal } from '@angular/core'
import { TestBed } from '@angular/core/testing'
import { describe, expect, vi } from 'vitest'
import { By } from '@angular/platform-browser'
Expand Down Expand Up @@ -453,4 +453,45 @@ describe('injectMutation', () => {

await expect(() => mutateAsync()).rejects.toThrowError(err)
})

test('should execute callback in injection context', async () => {
const errorSpy = vi.fn()
@Injectable()
class FakeService {
updateData(name: string) {
return Promise.resolve(name)
}
}

@Component({
selector: 'app-fake',
template: ``,
standalone: true,
providers: [FakeService],
})
class FakeComponent {
mutation = injectMutation(() => {
try {
const service = inject(FakeService)
return {
mutationFn: (name: string) => service.updateData(name),
}
} catch (e) {
errorSpy(e)
throw e
}
})
}

const fixture = TestBed.createComponent(FakeComponent)
fixture.detectChanges()

// check if injection contexts persist in a different task
await new Promise<void>((resolve) => queueMicrotask(() => resolve()))

expect(
await fixture.componentInstance.mutation.mutateAsync('test'),
).toEqual('test')
expect(errorSpy).not.toHaveBeenCalled()
})
})
6 changes: 5 additions & 1 deletion packages/angular-query-experimental/src/inject-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,11 @@ export function injectMutation<
}

effect(() => {
observer.setOptions(optionsFn(queryClient))
observer.setOptions(
runInInjectionContext(currentInjector, () =>
optionsFn(queryClient),
),
)
})

const result = signal(observer.getCurrentResult())
Expand Down

0 comments on commit cc47417

Please sign in to comment.