Skip to content

Commit

Permalink
feat(angular-query): lazyInit test and init without reactive context
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardoperra committed Mar 4, 2024
1 parent 8012b4f commit 9449e53
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,7 @@ export function setFixtureSignalInputs<T extends NonNullable<unknown>>(
componentFixture.detectChanges()
}
}

export async function flushQueue() {
await new Promise(setImmediate)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, expect, test } from 'vitest'
import {
ChangeDetectionStrategy,
Component,
type WritableSignal,
computed,
effect,
input,
signal,
} from '@angular/core'
import { TestBed } from '@angular/core/testing'
import { flushQueue, setFixtureSignalInputs } from '../../test-utils'
import { lazyInit } from '../../../util/lazy-init/lazy-init'

describe('lazyInit', () => {
test('should init lazily in next tick when not accessing manually', async () => {
const mockFn = vi.fn()

TestBed.runInInjectionContext(() => {
lazyInit(() => {
mockFn()
return {
data: signal(true),
}
})
})

expect(mockFn).not.toHaveBeenCalled()

await new Promise(setImmediate)

expect(mockFn).toHaveBeenCalled()
})

test('should init eagerly accessing manually', async () => {
const mockFn = vi.fn()

TestBed.runInInjectionContext(() => {
const lazySignal = lazyInit(() => {
mockFn()
return {
data: signal(true),
}
})

lazySignal.data()

console.log(lazySignal)
})

expect(mockFn).toHaveBeenCalled()
})

test('should init lazily and only once', async () => {
const initCallFn = vi.fn()
const registerDataValue = vi.fn<[number]>()

let value!: { data: WritableSignal<number> }
const outerSignal = signal(0)

TestBed.runInInjectionContext(() => {
value = lazyInit(() => {
initCallFn()

void outerSignal()

return { data: signal(0) }
})

effect(() => registerDataValue(value.data()))
})

value.data()

await flushQueue()

expect(outerSignal).toBeDefined()

expect(initCallFn).toHaveBeenCalledTimes(1)

outerSignal.set(1)
await flushQueue()
outerSignal.set(2)
await flushQueue()
value.data.set(4)
await flushQueue()

expect(initCallFn).toHaveBeenCalledTimes(1)
expect(registerDataValue).toHaveBeenCalledTimes(2)
})

test('should support required signal input', async () => {
@Component({
standalone: true,
template: `{{ call }} - {{ lazySignal.data() }}`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
class Test {
readonly title = input.required<string>()
call = 0

lazySignal = lazyInit(() => {
this.call++
return {
data: computed(() => this.title()),
}
})
}

const fixture = TestBed.createComponent(Test)

setFixtureSignalInputs(fixture, { title: 'newValue' })
expect(fixture.debugElement.nativeElement.textContent).toBe('0 - newValue')
await flushQueue()

setFixtureSignalInputs(fixture, { title: 'updatedValue' })
expect(fixture.debugElement.nativeElement.textContent).toBe(
'1 - updatedValue',
)

setFixtureSignalInputs(fixture, { title: 'newUpdatedValue' })
expect(fixture.debugElement.nativeElement.textContent).toBe(
'1 - newUpdatedValue',
)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ import {
} from '@angular/core'
import { TestBed } from '@angular/core/testing'
import { lazySignalInitializer } from '../../../util/lazy-signal-initializer/lazy-signal-initializer'
import { setFixtureSignalInputs } from '../../test-utils'

async function flushQueue() {
await new Promise(setImmediate)
}
import { flushQueue, setFixtureSignalInputs } from '../../test-utils'

describe('lazySignalInitializer', () => {
test('should init lazily in next tick when not accessing manually', async () => {
Expand Down Expand Up @@ -69,9 +65,7 @@ describe('lazySignalInitializer', () => {
effect(() => registerEffectValue(value()))
})

expect(initCallFn).toHaveBeenCalledTimes(0)
expect(outerSignal).toBeDefined()
expect(innerSignal).not.toBeDefined()
value()

await flushQueue()

Expand Down
27 changes: 12 additions & 15 deletions packages/angular-query-experimental/src/create-base-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from '@angular/core'
import { notifyManager } from '@tanstack/query-core'
import { signalProxy } from './signal-proxy'
import { lazyInit } from './lazy-init'
import { lazyInit } from './util/lazy-init/lazy-init'
import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types'

Expand Down Expand Up @@ -67,20 +67,17 @@ export function createBaseQuery<
observer.getOptimisticResult(defaultedOptionsSignal()),
)

// Effects should not be called inside reactive contexts
untracked(() =>
effect(() => {
const defaultedOptions = defaultedOptionsSignal()
observer.setOptions(defaultedOptions, {
// Do not notify on updates because of changes in the options because
// these changes should already be reflected in the optimistic result.
listeners: false,
})
untracked(() => {
resultSignal.set(observer.getOptimisticResult(defaultedOptions))
})
}),
)
effect(() => {
const defaultedOptions = defaultedOptionsSignal()
observer.setOptions(defaultedOptions, {
// Do not notify on updates because of changes in the options because
// these changes should already be reflected in the optimistic result.
listeners: false,
})
untracked(() => {
resultSignal.set(observer.getOptimisticResult(defaultedOptions))
})
})

// observer.trackResult is not used as this optimization is not needed for Angular
const unsubscribe = observer.subscribe(
Expand Down
24 changes: 11 additions & 13 deletions packages/angular-query-experimental/src/inject-mutation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,17 @@ export function injectMutationState<TResult = MutationState>(
getResult(mutationCache, mutationStateOptionsFn()),
)

untracked(() => {
effect(
() => {
const mutationStateOptions = mutationStateOptionsFn()
untracked(() => {
// Setting the signal from an effect because it's both 'computed' from options()
// and needs to be set imperatively in the mutationCache listener.
result.set(getResult(mutationCache, mutationStateOptions))
})
},
{ injector },
)
})
effect(
() => {
const mutationStateOptions = mutationStateOptionsFn()
untracked(() => {
// Setting the signal from an effect because it's both 'computed' from options()
// and needs to be set imperatively in the mutationCache listener.
result.set(getResult(mutationCache, mutationStateOptions))
})
},
{ injector },
)

const unsubscribe = mutationCache.subscribe(
notifyManager.batchCalls(() => {
Expand Down
10 changes: 3 additions & 7 deletions packages/angular-query-experimental/src/inject-mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import {
inject,
runInInjectionContext,
signal,
untracked,
} from '@angular/core'
import { MutationObserver, notifyManager } from '@tanstack/query-core'
import { assertInjector } from './util/assert-injector/assert-injector'
import { signalProxy } from './signal-proxy'
import { injectQueryClient } from './inject-query-client'
import { noop } from './util'

import { lazyInit } from './lazy-init'
import { lazyInit } from './util/lazy-init/lazy-init'
import type { DefaultError, QueryClient } from '@tanstack/query-core'
import type {
CreateMutateFunction,
Expand Down Expand Up @@ -55,11 +54,8 @@ export function injectMutation<
observer.mutate(variables, mutateOptions).catch(noop)
}

// Effects should not be called inside reactive contexts
untracked(() => {
effect(() => {
observer.setOptions(options(queryClient))
})
effect(() => {
observer.setOptions(options(queryClient))
})

const result = signal(observer.getCurrentResult())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { untracked } from '@angular/core'

export function lazyInit<T extends object>(initializer: () => T): T {
let object: T | null = null

const initializeObject = () => {
if (!object) {
object = initializer()
object = untracked(() => initializer())
}
}

Expand Down

0 comments on commit 9449e53

Please sign in to comment.