@@ -14,11 +14,10 @@ hide_title: true
14
14
15
15
## Key features
16
16
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
22
21
23
22
## Install
24
23
@@ -28,16 +27,278 @@ npm install --save @rx-angular/state
28
27
yarn add @rx-angular/state
29
28
```
30
29
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 )
32
259
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)
34
262
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
+ );
36
273
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
+ ```
38
303
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