@@ -7,37 +7,269 @@ hide_title: true
7
7
8
8
# @rx-angular/state /selections
9
9
10
- [ ![ npm] ( https://img.shields.io/npm/v/%40rx-angular%2Fcdk.svg )] ( https://www.npmjs.com/package/%40rx-angular%2Fcdk )
11
- ![ rx-angular CI] ( https://github.com/rx-angular/rx-angular/workflows/rx-angular%20CI/badge.svg?branch=master )
10
+ ## Motivation
11
+
12
+ ![ Selections (1)] ( https://user-images.githubusercontent.com/10064416/152422745-b3d8e094-d0f0-4810-b1b2-5f81fae25938.png )
12
13
13
- > This package provides a set of RxJS operators to select the state in an efficient way.
14
+ When managing state you want to maintain a core unit of data.
15
+ This data is then later on distributed to multiple places in your component template (local) or whole app (global).
14
16
15
- ## Key features
17
+ We can forward this state to their consumers directly or compute specific derivations (selections) for the core unit.
16
18
17
- - ✅
19
+ As an example we could think of the following shape:
18
20
19
- ## Demos:
21
+ ** A list and a list title **
20
22
21
- - ⚡ GitHub
23
+ ``` typescript
24
+ interface GlobalModel {
25
+ title: string ;
26
+ list: Array <{ id: number ; date: Date }>;
27
+ }
28
+ ```
22
29
23
- ## Install
30
+ This data is consumed in different screens:
24
31
25
- ``` bash
26
- npm install --save @rx-angular/state
27
- # or
28
- yarn add @rx-angular/state
32
+ ** A list of all items sorted by id**
33
+
34
+ ``` typescript
35
+ interface SelectionScreen1 {
36
+ title: string ;
37
+ sortDirection: ' asc' | ' desc' | ' none' ;
38
+ sortedList: Array <{ id: number }>;
39
+ }
29
40
```
30
41
31
- ## Documentation
42
+ ** A list of items filtered by date **
32
43
33
- - [ Selections] ( https://rx-angular.io/docs/state/selections )
44
+ ``` typescript
45
+ interface SelectionScreen2 {
46
+ title: string ;
47
+ startingDate: Date ;
48
+ filteredList: { id: number };
49
+ }
50
+ ```
34
51
35
- ## Motivation
52
+ The 2 rendered lists are a derivation, a modified version of the core set of items.
53
+ One time they are displayed in a sorted order, the other time only filtered subset of the items.
54
+
55
+ > ** Hint:**
56
+ > Derivations are always redundant information of our core data and therefore should not get stored,
57
+ > but cached in the derivation logic.
58
+
59
+ ![ Selections (2)] ( https://user-images.githubusercontent.com/10064416/152422803-bfd07ab2-0a6f-4521-836e-b71677e11923.png )
60
+
61
+ As this process contains a lot of gotchas and possible pitfalls in terms of memory usage and performance this small helper library was created.
62
+
63
+ # Benefits
64
+
65
+ ![ Selections (3)] ( https://user-images.githubusercontent.com/10064416/152422856-a483a06c-84e0-4067-9eaa-f3bb54a0156d.png )
66
+
67
+ - Sophisticated set of helpers for any selection problem
68
+ - Enables lazy rendering
69
+ - Computes only distinct values
70
+ - Shares computed result with multiple subscriber
71
+ - Select distinct sub-sets
72
+ - Select from static values
73
+ - Fully tested
74
+ - Strongly typed
75
+
76
+ ![ Selections (5)] ( https://user-images.githubusercontent.com/10064416/152422955-cb89d198-1a69-450b-be84-29dd6c8c4fdb.png )
77
+
78
+ 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.
79
+
80
+ ![ Selections (4)] ( https://user-images.githubusercontent.com/10064416/152422883-0b5f6006-7929-4520-b0b2-79eb61e4eb08.png )
81
+
82
+ # Usage
83
+
84
+ ## select
85
+
86
+ ` 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.
87
+
88
+ ``` typescritp
89
+ // emissions:
90
+ // 0. - no emission ever happened
91
+ // 1. {a: 1} - incomplete state leads to `?` pollution in the template
92
+ // 2. {a: 1, b: 'a'} - render relevant emission
93
+ // 2. {a: 1, b: 'a'} - same instance emisssion
94
+ // 3. {a: 1, b: 'a', c: true} - render irrelevant change
95
+ // 4. {a: 1, b: 'b', c: true} - render relevant emission
96
+ const model$: Observable<Partial<{a: number, b: string, c: boolean}>>;
97
+ ```
98
+
99
+ ** Problem**
100
+
101
+ ``` html
102
+ <!--
103
+
104
+ Computes 2 times & Renders 0. ❌; 1. ❌; 2. ✅; 3. ❌; .4 ✅
105
+ -->
106
+ <div *rxLet =" model$; let vm" >B: {{vm?.b}}</div >
107
+ B: {{(model$ | push)?.b}}
108
+ ```
109
+
110
+ ### single property short hand
111
+
112
+ ``` typescritp
113
+ const vm$ = model$.pipe(select('b'));
114
+ ```
115
+
116
+ ``` html
117
+ <!--
118
+ Computes 1 time & Renders 2. ✅; .4 ✅
119
+ -->
120
+ <div *rxLet =" model$; let vm" >B: {{vm.b}}</div >
121
+ B: {{(model$ | push).b}}
122
+ ```
123
+
124
+ ### single operators
125
+
126
+ ``` typescritp
127
+ const vm$: Observable<> = model$.pipe(select(map(({b}) => b === 'a')));
128
+ ```
129
+
130
+ ``` html
131
+ <!--
132
+ Computes 1 time & Renders 2. ✅; .4 ✅
133
+ -->
134
+ <div *rxLet =" model$; let vm" >B: {{vm.b}}</div >
135
+ B: {{(model$ | push).b}}
136
+ ```
36
137
37
- TBD
138
+ ## selectSlice
139
+
140
+ TODO
141
+
142
+ ## distinctUntilSomeChanges
143
+
144
+ TODO
145
+
146
+ # Advanced derivation architecture
147
+
148
+ ** The problem**
149
+
150
+ We have the following state sources to manage:
151
+
152
+ - the list of products received form global state - ` Product[] `
153
+ - the title of the list including it's number of children computen in the component class - ` string `
154
+ - the sort direction triggered over a UI element click - ` boolean `
155
+
156
+ A setup of the compoents class based on ` RxState ` could look like this:
157
+
158
+ ``` typescript
159
+ @Component ({
160
+ selector: ' app-problem' ,
161
+ template: `
162
+ <ng-container *rxLet="viewModel$; let vm">
163
+ <h1>{{vm.title}} - {{vm.sortDirection}}</h1>
164
+ <ul>
165
+ <li *ngFor="let item of vm.sortedList">{{item}}</li>
166
+ </ul>
167
+ </ng-container>
168
+ ` ,
169
+ providers: [RxState ],
170
+ })
171
+ export class ProblemComponent {
172
+
173
+ viewModel$: Observable <ViewModel >; // ???
174
+
175
+ constructor (private globalState : GlobalState , private state : RxState <Model >) {
176
+ this .state .connect (' title' , this .globalState .title$ );
177
+ this .state .connect (' products' , this .globalState .products$ );
178
+ }
179
+
180
+ toggleSort() {
181
+ this .state .set (' sort' , ({sort }) => ! sort ))
182
+ }
183
+ }
184
+
185
+ ```
186
+
187
+ In a components template we want to render the the UI for the above explained view model ` SelectionScreen1 ` .
188
+
189
+ ``` typescript
190
+ interface SelectionScreen1 {
191
+ title: string ;
192
+ sortDirection: ' asc' | ' desc' | ' none' ;
193
+ sortedList: Array <{ id: number }>;
194
+ }
195
+ ```
196
+
197
+ A common implementations looks like this:
198
+
199
+ ``` typescript
200
+ // template removed for brevity
201
+ export class ProblemComponent {
202
+ private sortedList$ = this .state .select (
203
+ selectSlice ([' sortDirection' , ' list' ]),
204
+ map (() => {
205
+ // sort `list` by `sortDirection` to `sortedList` here
206
+ return sortedList ;
207
+ })
208
+ );
209
+
210
+ viewModel$ = this .state .select (
211
+ selectSlice ([' title' , ' sortedList' , ' sortDirection' ])
212
+ );
213
+
214
+ // ❌ BAD: modle viewmodel mix up 👇
215
+ constructor (
216
+ private globalState : GlobalState ,
217
+ private state : RxState <Model & Pick <ViewModel , ' sortedList' >>
218
+ ) {
219
+ // ...
220
+
221
+ // ❌ BAD: store derived state 👇
222
+ this .state .connect (' sortedList' , this .sortedList$ );
223
+ }
224
+
225
+ // ...
226
+ }
227
+ ```
228
+
229
+ ![ Selections (6)] ( https://user-images.githubusercontent.com/10064416/152422999-db8260f0-69e1-4d99-b6ac-b2b1d043b4b7.png )
230
+
231
+ By removing the sorted list form the state and moving it into the selection
232
+ 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)
233
+
234
+ ``` typescript
235
+ // template removed for brevity
236
+ export class ProblemComponent {
237
+ private sortedSlice$ = this .state .select (
238
+ selectSlice ([' sortDirection' , ' list' ]),
239
+ map (({ list , sortDirection }) => {
240
+ // sort `list` by `sortDirection` to `sortedList` here
241
+ return { sortDirection , sortedList };
242
+ })
243
+ );
244
+
245
+ // ✔ GOOD: Derive view model from model 👇
246
+ viewModel$ = smosh ({ title: this .state .select (' title' ) }, this .sortedSlice$ );
247
+
248
+ // target API
249
+ viewModel$ = smosh (
250
+ {
251
+ prop1: ' prop1' , // string
252
+ prop2: prop1$ , // Observable<string>
253
+ },
254
+ slice1$ , // Observable<{prop3: 3}>
255
+ slice2$ // Observable<{prop4: 'four'}>,
256
+ // durationSelector$ (optional)
257
+ );
258
+
259
+ // ✔ GOOD: Derive view model from model 👇
260
+ viewModel$ = smosh (
261
+ {
262
+ title: this .state .select (' title' ),
263
+ },
264
+ [this .sortedSlice$ ]
265
+ );
266
+
267
+ constructor (private globalState : GlobalState , private state : RxState <Model >) {
268
+ // ...
269
+ }
270
+
271
+ // ...
272
+ }
273
+ ```
38
274
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 )
275
+ ![ Selections (7)] ( https://user-images.githubusercontent.com/10064416/152423026-d23326c2-97d5-4bd0-9015-f498c3fc0e55.png )
0 commit comments