Skip to content

Commit 0b1a4b9

Browse files
docs: deprecate withFeatureFactory in favor of withFeature (#167)
1 parent d260a26 commit 0b1a4b9

File tree

3 files changed

+15
-203
lines changed

3 files changed

+15
-203
lines changed

docs/docs/extensions.md

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ It offers extensions like:
99
- [⭐️ Devtools](./with-devtools): Integration into Redux Devtools
1010
- [Conditional Features](./with-conditional): Allows adding features to the store conditionally
1111
- [DataService](./with-data-service): Builds on top of `withEntities` and adds the backend synchronization to it
12-
- [Feature Factory](./with-feature-factory): Allows passing properties, methods, or signals from a SignalStore to a custom feature (`signalStoreFeature`).
1312
- [Immutable State Protection](./with-immutable-state): Protects the state from being mutated outside or inside the Store.
1413
- [Redux](./with-redux): Possibility to use the Redux Pattern (Reducer, Actions, Effects)
1514
- [Reset](./with-reset): Adds a `resetState` method to your store

docs/docs/with-feature-factory.md

+13-202
Original file line numberDiff line numberDiff line change
@@ -3,215 +3,26 @@ title: withFeatureFactory()
33
---
44

55
```typescript
6+
// DEPRECATED
67
import { withFeatureFactory } from '@angular-architects/ngrx-toolkit';
7-
```
8-
9-
The `withFeatureFactory()` function allows passing properties, methods, or signals from a SignalStore to a feature. It is an advanced feature, primarily targeted for library authors for SignalStore features.
10-
11-
Its usage is very simple. It is a function which gets the current store:
12-
13-
```typescript
14-
import { withFeatureFactory } from '@angular-architects/ngrx-toolkit';
15-
16-
function withSum(a: Signal<number>, b: Signal<number>) {
17-
return signalStoreFeature(
18-
withComputed(() => ({
19-
sum: computed(() => a() + b()),
20-
}))
21-
);
22-
}
238

24-
signalStore(
25-
withState({ a: 1, b: 2 }),
26-
withFeatureFactory((store) => withSum(store.a, store.b))
27-
);
9+
// Use this instead
10+
import { withFeature } from '@ngrx/signals'
2811
```
2912

30-
## Use Case 1: Mismatching Input Constraints
31-
32-
`signalStoreFeature` can define input constraints that must be fulfilled by the SignalStore calling the feature. For example, a method `load` needs to be present to fetch data. The default implementation would be:
33-
34-
```typescript
35-
type Entity = {
36-
id: number;
37-
name: string;
38-
};
39-
40-
function withEntityLoader() {
41-
return signalStoreFeature(
42-
type<{
43-
methods: {
44-
load: (id: number) => Promise<Entity>;
45-
};
46-
}>(),
47-
withState({
48-
entity: undefined as Entity | undefined,
49-
}),
50-
withMethods((store) => ({
51-
async setEntityId(id: number) {
52-
const entity = await store.load(id);
53-
patchState(store, { entity });
54-
},
55-
}))
56-
);
57-
}
58-
```
59-
60-
The usage of `withEntityLoader` would be:
61-
62-
```typescript
63-
signalStore(
64-
withMethods((store) => ({
65-
load(id: number): Promise<Entity> {
66-
// some dummy implementation
67-
return Promise.resolve({ id, name: 'John' });
68-
},
69-
})),
70-
withEntityLoader()
71-
);
72-
```
73-
74-
A common issue with generic features is that the input constraints are not fulfilled exactly. If the existing `load` method would return an `Observable<Entity>`, we would have to rename that one and come up with a `load` returning `Promise<Entitiy>`. Renaming an existing method might not always be an option. Beyond that, what if two different features require a `load` method with different return types?
75-
76-
Another aspect is that we probably want to encapsulate the load method since it is an internal one. The current options don't allow that, unless the `withEntityLoader` explicitly defines a `_load` method.
77-
78-
For example:
79-
80-
```typescript
81-
signalStore(
82-
withMethods((store) => ({
83-
load(id: number): Observable<Entity> {
84-
return of({ id, name: 'John' });
85-
},
86-
})),
87-
withEntityLoader()
88-
);
89-
```
90-
91-
`withFeatureFactory` solves those issues by mapping the existing method to whatever `withEntityLoader` requires. `withEntityLoader` needs to move the `load` method dependency to an argument of the function:
92-
93-
```typescript
94-
function withEntityLoader(load: (id: number) => Promise<Entity>) {
95-
return signalStoreFeature(
96-
withState({
97-
entity: undefined as Entity | undefined,
98-
}),
99-
withMethods((store) => ({
100-
async setEntityId(id: number) {
101-
const entity = await load(id);
102-
patchState(store, { entity });
103-
},
104-
}))
105-
);
106-
}
107-
```
108-
109-
`withFeatureFactory` can now map the existing `load` method to the required one.
110-
111-
```typescript
112-
import { withFeatureFactory } from '@angular-architects/ngrx-toolkit';
113-
114-
const store = signalStore(
115-
withMethods((store) => ({
116-
load(id: number): Observable<Entity> {
117-
// some dummy implementation
118-
return of({ id, name: 'John' });
119-
},
120-
})),
121-
withFeatureFactory((store) => withEntityLoader((id) => firstValueFrom(store.load(id))))
122-
);
123-
```
124-
125-
## Use Case 2: Generic features with Input Constraints
126-
127-
Another potential issue with advanced features in a SignalStore is that multiple
128-
features with input constraints cannot use generic types.
129-
130-
For example, `withEntityLoader` is a generic feature that allows the caller to
131-
define the entity type. Alongside `withEntityLoader`, there's another feature,
132-
`withOptionalState`, which has input constraints as well.
133-
134-
Due to [certain TypeScript limitations](https://ngrx.io/guide/signals/signal-store/custom-store-features#known-typescript-issues),
135-
the following code will not compile:
136-
137-
```typescript
138-
function withEntityLoader<T>() {
139-
return signalStoreFeature(
140-
type<{
141-
methods: {
142-
load: (id: number) => Promise<T>;
143-
};
144-
}>(),
145-
withState({
146-
entity: undefined as T | undefined,
147-
}),
148-
withMethods((store) => ({
149-
async setEntityId(id: number) {
150-
const entity = await store.load(id);
151-
patchState(store, { entity });
152-
},
153-
}))
154-
);
155-
}
156-
157-
function withOptionalState<T>() {
158-
return signalStoreFeature(
159-
type<{ methods: { foo: () => string } }>(),
160-
withState({
161-
state: undefined as T | undefined,
162-
})
163-
);
164-
}
13+
The `withFeatureFactory()` function allows passing properties, methods, or signals from a SignalStore to a feature. It is an advanced feature, primarily targeted for library authors for SignalStore features.
16514

166-
signalStore(
167-
withMethods((store) => ({
168-
foo: () => 'bar',
169-
load(id: number): Promise<Entity> {
170-
// some dummy implementation
171-
return Promise.resolve({ id, name: 'John' });
172-
},
173-
})),
174-
withOptionalState<Entity>(),
175-
withEntityLoader<Entity>()
176-
);
177-
```
15+
:::warning
16+
## Deprecation
17817

179-
Again, `withFeatureFactory` can solve this issue by replacing the input constraint with a function parameter:
18+
Use `import { withFeature } from '@ngrx/signals'` instead.
18019

181-
```typescript
182-
import { withFeatureFactory } from '@angular-architects/ngrx-toolkit';
20+
[`withFeature`](https://ngrx.io/guide/signals/signal-store/custom-store-features#connecting-a-custom-feature-with-the-store) is the successor of the toolkit's `withFeatureFactory`.
21+
- Available starting in `ngrx/signals` 19.1
22+
- NgRx PR: ["feat(signals): add `withFeature` #4739"](https://github.com/ngrx/platform/pull/4739)
23+
- NgRx [documentation section](https://ngrx.io/guide/signals/signal-store/custom-store-features#connecting-a-custom-feature-with-the-store) on `withFeature`
18324

184-
function withEntityLoader<T>(loader: (id: number) => Promise<T>) {
185-
return signalStoreFeature(
186-
withState({
187-
entity: undefined as T | undefined,
188-
}),
189-
withMethods((store) => ({
190-
async setEntityId(id: number) {
191-
const entity = await loader(id);
192-
patchState(store, { entity });
193-
},
194-
}))
195-
);
196-
}
25+
In the future, `withFeatureFactory` will likely be removed, provided a right migration path is prepared. Watch out for PRs, and see [the PR that deprecates `withFeatureFactory` for the initial plan for handling the removal](https://github.com/angular-architects/ngrx-toolkit/pull/167#pullrequestreview-2735443379).
26+
:::
19727

198-
function withOptionalState<T>(foo: () => string) {
199-
return signalStoreFeature(
200-
withState({
201-
state: undefined as T | undefined,
202-
})
203-
);
204-
}
20528

206-
signalStore(
207-
withMethods((store) => ({
208-
foo: () => 'bar',
209-
load(id: number): Promise<Entity> {
210-
// some dummy implementation
211-
return Promise.resolve({ id, name: 'John' });
212-
},
213-
})),
214-
withFeatureFactory((store) => withOptionalState<Entity>(store.foo.bind(store))),
215-
withFeatureFactory((store) => withEntityLoader<Entity>(store.load.bind(store)))
216-
);
217-
```

libs/ngrx-toolkit/src/lib/with-feature-factory.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ type StoreForFactory<Input extends SignalStoreFeatureResult> = StateSignals<
1111
Input['methods'];
1212

1313
/**
14+
* @deprecated Use `import { withFeature } from '@ngrx/signals'` instead, starting with `ngrx/signals` 19.1: https://ngrx.io/guide/signals/signal-store/custom-store-features#connecting-a-custom-feature-with-the-store
15+
*
1416
* Allows to pass properties, methods, or signals from a SignalStore
1517
* to a feature.
1618
*

0 commit comments

Comments
 (0)