Replies: 4 comments 6 replies
-
UPDATE: ahh, I see from #4499 that methods aren't exposed in You should be able to achieve this by defining multiple factory functions — each subsequent factory function has access to the previous declarations. Roughly speaking: const Store = signalStore(
withState(…),
withComputed(…),
withMethods(…),
withComputed(…),
// etc.
) |
Beta Was this translation helpful? Give feedback.
-
@jits I'm referring to quite advanced usages like abstract example below import { computed, inject, type Signal, type Type } from "@angular/core";
import {
patchState,
signalStore,
type SignalStoreFeature,
type SignalStoreFeatureResult,
withComputed,
withMethods,
withState,
} from "@ngrx/signals";
import { lowerFirst } from "lodash-es";
/* eslint-disable @typescript-eslint/ban-types */
type Connected<T> = (() => T) & T;
type UnwrapSignal<T> = T extends Signal<infer U> ? U : never;
const connect = <T extends object, U extends Connected<T>>(value: T): U =>
new Proxy(() => value, {
get: (_, prop) => Reflect.get(value, prop),
has: (_, prop) => Reflect.has(value, prop),
}) as U;
export const withConnectedStore = <
ConnectedStore extends Type<unknown>,
Name extends string,
Input extends SignalStoreFeatureResult = SignalStoreFeatureResult,
>(
name: Name,
connectedStoreRef: ConnectedStore
): SignalStoreFeature<
Input,
{ state: {}; computed: {}; methods: Record<typeof prop, Connected<NoInfer<InstanceType<ConnectedStore>>>> }
> => {
const prop = `${lowerFirst(name)}Store` as `${Uncapitalize<Name>}Store`;
return (store) => {
const connectedStore = inject(connectedStoreRef);
return {
...store,
methods: {
...store.methods,
[prop]: connect(connectedStore as object),
} as Record<typeof prop, Connected<NoInfer<InstanceType<ConnectedStore>>>>,
};
};
};
const OrderStore = signalStore(
withState<{
by: string | null;
direction: "ASC" | "DESC";
}>({
by: null,
direction: "ASC",
}),
withMethods((store) => ({
change(by: string | null, direction: "ASC" | "DESC"): void {
return patchState(store, { by, direction });
},
applyParams(params: URLSearchParams): URLSearchParams {
const orderBy = store.by();
const orderDirection = store.direction();
if (orderBy) {
params.append("orderBy", `${orderBy}:${orderDirection}`);
}
return params;
},
}))
);
const FiltersStore = signalStore(
withState<{
name: string | null;
status: "created" | "approved" | "active" | null;
}>({
name: null,
status: null,
}),
withMethods((store) => ({
setFilter<T extends "name" | "status">(prop: T, value: UnwrapSignal<(typeof store)[T]>): void {
return patchState(store, { [prop]: value });
},
applyParams(params: URLSearchParams): URLSearchParams {
const nameFilter = store.name();
const statusFilter = store.status();
if (nameFilter) {
params.append("name", nameFilter);
}
if (statusFilter) {
params.append("status", statusFilter);
}
return params;
},
}))
);
const DataSourceStore = signalStore(
withState({
isLoading: false,
isLoaded: false,
data: [],
}),
withConnectedStore("order", OrderStore),
withConnectedStore("filters", FiltersStore),
withComputed((store) => ({
query: computed<string>(() => {
const params = new URLSearchParams();
// I know that those stores could be injected here using `inject()`, it serves just as an example.
// In my real application I'm connecting here Tanstack Angular Query
store.orderStore.applyParams(params);
store.filtersStore.applyParams(params);
return params.toString();
}),
}))
// withMethods(() => ({
// methods required to fetch and store data etc.
// }))
); |
Beta Was this translation helpful? Give feedback.
-
Here are my thoughts:
|
Beta Was this translation helpful? Give feedback.
-
Closing as RFC #4503 would bring requested capabilities |
Beta Was this translation helpful? Give feedback.
-
Currently, Signal Store does not allow
methods
to be used insidewithComputed
to prevent developers from "footgunning" themselves. But there are valid real world use cases wheremethods
need to be used there. For example, to connect to another store or external resource and not having it as part of state (to not be wrapped into Signal or DeepSignal).I strongly believe that libraries shouldn't do the thinking for developers and save them from stupid mistakes. For that there is eslint and eslint rule could be used to warn them about those potential "footguns". But the library shouldn't restrict its use.
Beta Was this translation helpful? Give feedback.
All reactions