Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { signal } from '@angular/core'
import { isReactive } from '@angular/core/primitives/signals'
import { describe } from 'vitest'
import { signalProxy } from '../signal-proxy'

describe('signalProxy', () => {
const inputSignal = signal({ fn: () => 'bar', baz: 'qux' })
const proxy = signalProxy(inputSignal)

it('should have computed fields', () => {
expect(proxy.baz()).toEqual('qux')
expect(isReactive(proxy.baz)).toBe(true)
})

it('should pass through functions as-is', () => {
expect(proxy.fn()).toEqual('bar')
expect(isReactive(proxy.fn)).toBe(false)
})

it('supports "in" operator', () => {
expect('baz' in proxy).toBe(true)
expect('foo' in proxy).toBe(false)
})

it('supports "Object.keys"', () => {
expect(Object.keys(proxy)).toEqual(['fn', 'baz'])
})
})
4 changes: 2 additions & 2 deletions packages/angular-query-experimental/src/create-base-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
inject,
signal,
} from '@angular/core'
import { createResultStateSignalProxy } from './query-proxy'
import { signalProxy } from './signal-proxy'
import type { QueryClient, QueryKey, QueryObserver } from '@tanstack/query-core'
import type { CreateBaseQueryOptions, CreateBaseQueryResult } from './types'

Expand Down Expand Up @@ -81,5 +81,5 @@ export function createBaseQuery<
const unsubscribe = observer.subscribe(resultSignal.set)
destroyRef.onDestroy(unsubscribe)

return createResultStateSignalProxy<TData, TError>(resultSignal)
return signalProxy(resultSignal)
}
51 changes: 0 additions & 51 deletions packages/angular-query-experimental/src/query-proxy.ts

This file was deleted.

48 changes: 48 additions & 0 deletions packages/angular-query-experimental/src/signal-proxy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { computed, untracked } from '@angular/core'
import type { Signal } from '@angular/core'

type MapToSignals<T> = {
[K in keyof T]: T[K] extends Function ? T[K] : Signal<T[K]>
}

/**
* Exposes fields of an object passed via an Angular `Signal` as `Computed` signals.
*
* Functions on the object are passed through as-is.
*
* @param inputSignal - `Signal` that must return an object.
*
*/
export function signalProxy<TInput extends Record<string | symbol, any>>(
inputSignal: Signal<TInput>,
) {
const internalState = {} as MapToSignals<TInput>

return new Proxy<MapToSignals<TInput>>(internalState, {
get(target, prop) {
// first check if we have it in our internal state and return it
const computedField = target[prop]
if (computedField) return computedField

// then, check if it's a function on the resultState and return it
const targetField = untracked(inputSignal)[prop]
if (typeof targetField === 'function') return targetField

// finally, create a computed field, store it and return it
// @ts-ignore
return (target[prop] = computed(() => inputSignal()[prop]))
},
has(_, prop) {
return !!untracked(inputSignal)[prop]
},
ownKeys() {
return Reflect.ownKeys(untracked(inputSignal))
},
getOwnPropertyDescriptor() {
return {
enumerable: true,
configurable: true,
}
},
})
}