Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 71703a3

Browse files
committed
docs: add selection docs
1 parent ec364d8 commit 71703a3

File tree

2 files changed

+276
-15
lines changed

2 files changed

+276
-15
lines changed

apps/docs/docs/state/actions/actions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ hide_title: true
1616

1717
- ✅ Fully Typed
1818
- ✅ No-Boilerplate
19-
- ✅ Configurable transformations to have lines in the template
19+
- ✅ Configurable transformations to have less lines in the template
2020
- ✅ Minimal memory footprint through a Proxy object and lazy initialization
2121

2222
## Demos:

apps/docs/docs/state/selections/selections.mdx

Lines changed: 275 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ hide_title: true
1414
1515
## Key features
1616

17-
-
18-
19-
## Demos:
20-
21-
- ⚡ GitHub
17+
- ✅ Fully Typed
18+
- ✅ No-Boilerplate
19+
- ✅ Performant selections
20+
- ✅ Minimal memory footprint through a Proxy object and lazy initialization
2221

2322
## Install
2423

@@ -28,16 +27,278 @@ npm install --save @rx-angular/state
2827
yarn add @rx-angular/state
2928
```
3029

31-
## Documentation
30+
# Motivation
31+
32+
![Selections (1)](https://user-images.githubusercontent.com/10064416/152422745-b3d8e094-d0f0-4810-b1b2-5f81fae25938.png)
33+
34+
When managing state you want to maintain a core unit of data.
35+
This data is then later on distributed to multiple places in your component template (local) or whole app (global).
36+
37+
We can forward this state to their consumers directly or compute specific derivations (selections) for the core unit.
38+
39+
As an example we could think of the following shape:
40+
41+
**A list and a list title**
42+
43+
```typescript
44+
interface GlobalModel {
45+
title: string;
46+
list: Array<{ id: number; date: Date }>;
47+
}
48+
```
49+
50+
This data is consumed in different screens:
51+
52+
**A list of all items sorted by id**
53+
54+
```typescript
55+
interface SelectionScreen1 {
56+
title: string;
57+
sortDirection: 'asc' | 'desc' | 'none';
58+
sortedList: Array<{ id: number }>;
59+
}
60+
```
61+
62+
**A list of items filtered by date**
63+
64+
```typescript
65+
interface SelectionScreen2 {
66+
title: string;
67+
startingDate: Date;
68+
filteredList: { id: number };
69+
}
70+
```
71+
72+
The 2 rendered lists are a derivation, a modified version of the core set of items.
73+
One time they are displayed in a sorted order, the other time only filtered subset of the items.
74+
75+
> **Hint:**
76+
> Derivations are always redundant information of our core data and therefore should not get stored,
77+
> but cached in the derivation logic.
78+
79+
![Selections (2)](https://user-images.githubusercontent.com/10064416/152422803-bfd07ab2-0a6f-4521-836e-b71677e11923.png)
80+
81+
As this process contains a lot of gotchas and possible pitfalls in terms of memory usage and performance this small helper library was created.
82+
83+
# Benefits
84+
85+
![Selections (3)](https://user-images.githubusercontent.com/10064416/152422856-a483a06c-84e0-4067-9eaa-f3bb54a0156d.png)
86+
87+
- Sophisticated set of helpers for any selection problem
88+
- Enables lazy rendering
89+
- Computes only distinct values
90+
- Shares computed result with multiple subscriber
91+
- Select distinct sub-sets
92+
- Select from static values
93+
- Fully tested
94+
- Strongly typed
95+
96+
# Concepts
97+
98+
## Selection composition - lazy vs eager
99+
100+
## Selection composition - functional vs reactive
101+
102+
## Selection setup - Template vs Class
103+
104+
As Observables are cold their resulting stream will only get activated by a subscription.
105+
This leads to a situations called: "the late subscriber problem" or "the early subscriber problem". (LINK)
106+
107+
![Selections (5)](https://user-images.githubusercontent.com/10064416/152422955-cb89d198-1a69-450b-be84-29dd6c8c4fdb.png)
108+
109+
In most cases it's best to go with solving problems on the early subscriber side and be sure we never loose values that should render on the screen.
110+
111+
![Selections (4)](https://user-images.githubusercontent.com/10064416/152422883-0b5f6006-7929-4520-b0b2-79eb61e4eb08.png)
112+
113+
# Usage
114+
115+
## select
116+
117+
`select` is the stand-alone version of the `RxState#select` top level method. It helps to create default selection's from a changing state source.
118+
119+
```typescritp
120+
// emissions:
121+
// 0. - no emission ever happened
122+
// 1. {a: 1} - incomplete state leads to `?` pollution in the template
123+
// 2. {a: 1, b: 'a'} - render relevant emission
124+
// 2. {a: 1, b: 'a'} - same instance emisssion
125+
// 3. {a: 1, b: 'a', c: true} - render irrelevant change
126+
// 4. {a: 1, b: 'b', c: true} - render relevant emission
127+
const model$: Observable<Partial<{a: number, b: string, c: boolean}>>;
128+
```
129+
130+
**Problem**
131+
132+
```html
133+
<!--
134+
135+
Computes 2 times & Renders 0. ❌; 1. ❌; 2. ✅; 3. ❌; .4 ✅
136+
-->
137+
<div *rxLet="model$; let vm">B: {{vm?.b}}</div>
138+
B: {{(model$ | push)?.b}}
139+
```
140+
141+
### single property short hand
142+
143+
```typescritp
144+
const vm$ = model$.pipe(select('b'));
145+
```
146+
147+
```html
148+
<!--
149+
Computes 1 time & Renders 2. ✅; .4 ✅
150+
-->
151+
<div *rxLet="model$; let vm">B: {{vm.b}}</div>
152+
B: {{(model$ | push).b}}
153+
```
154+
155+
### single operators
156+
157+
```typescritp
158+
const vm$: Observable<> = model$.pipe(select(map(({b}) => b === 'a')));
159+
```
160+
161+
```html
162+
<!--
163+
Computes 1 time & Renders 2. ✅; .4 ✅
164+
-->
165+
<div *rxLet="model$; let vm">B: {{vm.b}}</div>
166+
B: {{(model$ | push).b}}
167+
```
168+
169+
## selectSlice
170+
171+
## smosh
172+
173+
## distinctUntilSomeChanges
174+
175+
# Advanced derivation architecture
176+
177+
**The problem**
178+
179+
We have the following state sources to manage:
180+
181+
- the list of products received form global state - `Product[]`
182+
- the title of the list including it's number of children computen in the component class - `string`
183+
- the sort direction triggered over a UI element click - `boolean`
184+
185+
A setup of the compoents class based on `RxState` could look like this:
186+
187+
```typescript
188+
@Component({
189+
selector: 'app-problem',
190+
template: `
191+
<ng-container *rxLet="viewModel$; let vm">
192+
<h1>{{vm.title}} - {{vm.sortDirection}}</h1>
193+
<ul>
194+
<li *ngFor="let item of vm.sortedList">{{item}}</li>
195+
</ul>
196+
</ng-container>
197+
`,
198+
providers: [RxState],
199+
})
200+
export class ProblemComponent {
201+
202+
viewModel$: Observable<ViewModel>; // ???
203+
204+
constructor(private globalState: GlobalState, private state: RxState<Model>) {
205+
this.state.connect('title', this.globalState.title$);
206+
this.state.connect('products', this.globalState.products$);
207+
}
208+
209+
toggleSort() {
210+
this.state.set('sort', ({sort}) => !sort))
211+
}
212+
}
213+
214+
```
215+
216+
In a components template we want to render the the UI for the above explained view model `SelectionScreen1`.
217+
218+
```typescript
219+
interface SelectionScreen1 {
220+
title: string;
221+
sortDirection: 'asc' | 'desc' | 'none';
222+
sortedList: Array<{ id: number }>;
223+
}
224+
```
225+
226+
A common implementations looks like this:
227+
228+
```typescript
229+
// template removed for brevity
230+
export class ProblemComponent {
231+
private sortedList$ = this.state.select(
232+
selectSlice(['sortDirection', 'list']),
233+
map(() => {
234+
// sort `list` by `sortDirection` to `sortedList` here
235+
return sortedList;
236+
})
237+
);
238+
239+
viewModel$ = this.state.select(
240+
selectSlice(['title', 'sortedList', 'sortDirection'])
241+
);
242+
243+
// ❌ BAD: modle viewmodel mix up 👇
244+
constructor(
245+
private globalState: GlobalState,
246+
private state: RxState<Model & Pick<ViewModel, 'sortedList'>>
247+
) {
248+
// ...
249+
250+
// ❌ BAD: store derived state 👇
251+
this.state.connect('sortedList', this.sortedList$);
252+
}
253+
254+
// ...
255+
}
256+
```
257+
258+
![Selections (6)](https://user-images.githubusercontent.com/10064416/152422999-db8260f0-69e1-4d99-b6ac-b2b1d043b4b7.png)
32259

33-
- [Selections](https://rx-angular.io/docs/state/selections)
260+
By removing the sorted list form the state and moving it into the selection
261+
we can clean up the state's typing and have a nice separation of which data is owned by the component (model) and which data is owned by the template (view model)
34262

35-
## Motivation
263+
```typescript
264+
// template removed for brevity
265+
export class ProblemComponent {
266+
private sortedSlice$ = this.state.select(
267+
selectSlice(['sortDirection', 'list']),
268+
map(({ list, sortDirection }) => {
269+
// sort `list` by `sortDirection` to `sortedList` here
270+
return { sortDirection, sortedList };
271+
})
272+
);
36273

37-
TBD
274+
// ✔ GOOD: Derive view model from model 👇
275+
viewModel$ = smosh({ title: this.state.select('title') }, this.sortedSlice$);
276+
277+
// target API
278+
viewModel$ = smosh(
279+
{
280+
prop1: 'prop1', // string
281+
prop2: prop1$, // Observable<string>
282+
},
283+
slice1$, // Observable<{prop3: 3}>
284+
slice2$ // Observable<{prop4: 'four'}>,
285+
// durationSelector$ (optional)
286+
);
287+
288+
// ✔ GOOD: Derive view model from model 👇
289+
viewModel$ = smosh(
290+
{
291+
title: this.state.select('title'),
292+
},
293+
[this.sortedSlice$]
294+
);
295+
296+
constructor(private globalState: GlobalState, private state: RxState<Model>) {
297+
// ...
298+
}
299+
300+
// ...
301+
}
302+
```
38303

39-
- [`Interfaces`](api/interfaces/)
40-
- [`distinctUntilSomeChanged`](../api/rxjs-operators/distinct-until-some-changed.md)
41-
- [`select`](../api/rxjs-operators/select.md)
42-
- [`selectSlice`](../api/rxjs-operators/select-slice.md)
43-
- [`stateful`](../api/rxjs-operators/stateful.md)
304+
![Selections (7)](https://user-images.githubusercontent.com/10064416/152423026-d23326c2-97d5-4bd0-9015-f498c3fc0e55.png)

0 commit comments

Comments
 (0)