-
-
Notifications
You must be signed in to change notification settings - Fork 208
feat: reintroduce smosh, add docs to selections #1192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
BioPhoton
wants to merge
29
commits into
main
Choose a base branch
from
reintro-accumulate-observable
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
d8f3ae2
feat: reintroduce smosh, add docs to selections
BioPhoton a5a0e49
8000
Update libs/state/selections/docs/Readme.md
BioPhoton 78becbd
Update libs/state/selections/docs/Readme.md
BioPhoton caed2fa
Update libs/state/selections/docs/Readme.md
BioPhoton 70652ab
Update libs/state/selections/docs/Readme.md
BioPhoton d622605
Update libs/state/selections/docs/Readme.md
BioPhoton ef3131f
Update libs/state/selections/docs/Readme.md
BioPhoton d2d52ff
Update libs/state/selections/docs/Readme.md
BioPhoton 0352d14
perf: reduce smosh, organize typings
BioPhoton f0401df
Merge branch 'main' into reintro-accumulate-observable
BioPhoton f4bdba7
Update libs/state/selections/docs/Readme.md
BioPhoton e09314d
Update libs/state/selections/docs/Readme.md
BioPhoton 6da1e2e
Update libs/state/selections/docs/Readme.md
BioPhoton ca28c9e
Update libs/state/selections/docs/Readme.md
BioPhoton 406cece
Update libs/state/selections/docs/Readme.md
BioPhoton 4b2d18e
Update libs/state/selections/docs/Readme.md
BioPhoton ba7cb50
Update libs/state/selections/docs/Readme.md
BioPhoton 041f90d
Merge branch 'main' into reintro-accumulate-observable
BioPhoton b7a02bd
Update libs/state/selections/docs/Readme.md
BioPhoton 163e81b
Update Readme.md
BioPhoton 119328b
Update Readme.md
BioPhoton 3893bb5
Update Readme.md
BioPhoton 910331e
refactor(state): wip selections add spreads to smosh
BioPhoton 7406ec6
refactor(state): wip selections add spreads to smosh
BioPhoton 94d8b02
Merge branch 'main' into reintro-accumulate-observable
BioPhoton 7d474c5
docs(state): selection
BioPhoton d8f4e5f
Update libs/state/selections/docs/Readme.md
BioPhoton d8f2ae1
Update libs/state/selections/docs/Readme.md
BioPhoton 0b1bda8
chore(state): make eslint happy
edbzn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
8000
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,210 @@ | ||
# Motivation | ||
|
||
TBD | ||
 | ||
|
||
* [`Interfaces`](./operators/interfaces.md) | ||
* [`distinctUntilSomeChanged`](./operators/distinct-until-some-changed.md) | ||
* [`select`](./operators/select.md) | ||
* [`selectSlice`](./operators/select-slice.md) | ||
* [`stateful`](./operators/stateful.md) | ||
When managing state you want to maintain a core unit of data. | ||
This data is then later on distributed to multiple places in your component template (local) or whole app (global). | ||
|
||
We can forward this state to their consumers directly or compute specific derivations (selections) for the core unit. | ||
|
||
As an example we could think of the following shape: | ||
|
||
**A list and a list title** | ||
```typescript | ||
interface GlobalModel { | ||
title: string; | ||
list: Array<{ id: number, date: Date }> | ||
} | ||
``` | ||
|
||
This data is consumed in different screens: | ||
|
||
**A list of all items sorted by id** | ||
```typescript | ||
interface SelectionScreen1 { | ||
title: string; | ||
sortDirection: 'asc' | 'desc' | 'none'; | ||
sortedList: Array<{ id: number }> | ||
} | ||
``` | ||
|
||
**A list of items filtered by date** | ||
```typescript | ||
interface SelectionScreen2 { | ||
title: string; | ||
startingDate: Date; | ||
filteredList: { id: number } | ||
} | ||
``` | ||
|
||
The 2 rendered lists are a derivation, a modified version of the core set of items. | ||
One time they are displayed in a sorted order, the other time only filtered subset of the items. | ||
|
||
> **Hint:** | ||
> Derivations are always redundant information of our core data and therefore should not get stored, | ||
> but cached in the derivation logic. | ||
|
||
 | ||
|
||
As this process contains a lot of gotchas and possible pitfalls in terms of memory usage and performance this small helper library was created. | ||
|
||
# Benefits | ||
|
||
 | ||
|
||
|
||
- Sophisticated set of helpers for any selection problem | ||
- Enables lazy rendering | ||
- Computes only distinct values | ||
- Shares computed result with multiple subscriber | ||
- Select distinct sub-sets | ||
- Select from static values | ||
- Fully Tested | ||
- Fully Typed | ||
|
||
## Selection owner - Template vs Class | ||
|
||
As Observables are cold their resulting stream will only get activated by a subscription. | ||
This leads to a situations called: "the late subscriber problem" or "the early subscriber problem". (LINK) | ||
|
||
 | ||
|
||
|
||
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. | ||
|
||
|
||
|
||
|
||
## Advanced derivation architecture | ||
|
||
**The problem** | ||
|
||
We have the following state sources to manage: | ||
- the list of products received form global state - `Product[]` | ||
- the title of the list including it's number of children computen in the component class - `string` | ||
- the sort direction triggered over a UI element click - `boolean` | ||
|
||
A setup of the compoents class based on `RxState` could look like this: | ||
|
||
```typescript | ||
@Component({ | ||
selector: 'app-problem', | ||
template: ` | ||
<ng-container *rxLet="viewModel$; let vm"> | ||
<h1>{{vm.title}} - {{vm.sortDirection}}</h1> | ||
<ul> | ||
<li *ngFor="let item of vm.sortedList">{{item}}</li> | ||
</ul> | ||
</ng-container> | ||
`, | ||
providers: [RxState], | ||
}) | ||
export class ProblemComponent { | ||
|
||
viewModel$: Observable<ViewModel>; // ??? | ||
|
||
constructor(private globalState: GlobalState, private state: RxState<Model>) { | ||
this.state.connect('title', this.globalState.title$); | ||
this.state.connect('products', this.globalState.products$); | ||
} | ||
|
||
toggleSort() { | ||
this.state.set('sort', ({sort}) => !sort)) | ||
} | ||
} | ||
|
||
``` | ||
|
||
In a components template we want to render the the UI for the above explained view model `SelectionScreen1`. | ||
|
||
```typescript | ||
interface SelectionScreen1 { | ||
title: string; | ||
sortDirection: 'asc' | 'desc' | 'none'; | ||
sortedList: Array<{ id: number }> | ||
} | ||
``` | ||
|
||
A common implementations looks like this: | ||
|
||
|
||
```typescript | ||
// template removed for brevity | ||
export class ProblemComponent { | ||
|
||
private sortedList$ = this.state.select( | ||
selectSlice(['sortDirection', 'list']), | ||
map(() => { | ||
// sort `list` by `sortDirection` to `sortedList` here | ||
return sortedList; | ||
}) | ||
); | ||
|
||
viewModel$ = this.state.select( | ||
selectSlice(['title', 'sortedList', 'sortDirection']) | ||
) | ||
|
||
// ❌ BAD: modle viewmodel mix up 👇 | ||
constructor(private globalState: GlobalState, private state: RxState<Model & Pick<ViewModel, 'sortedList'>>) { | ||
// ... | ||
|
||
// ❌ BAD: store derived state 👇 | ||
this.state.connect('sortedList', this.sortedList$); | ||
} | ||
|
||
// ... | ||
} | ||
|
||
``` | ||
|
||
 | ||
|
||
By removing the sorted list form the state and moving it into the selection | ||
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) | ||
|
||
```typescript | ||
// template removed for brevity | ||
export class ProblemComponent { | ||
|
||
private sortedSlice$ = this.state.select( | ||
selectSlice(['sortDirection', 'list']), | ||
map(({list, sortDirection}) => { | ||
// sort `list` by `sortDirection` to `sortedList` here | ||
return { sortDirection, sortedList }; | ||
}) | ||
); | ||
|
||
// ✔ GOOD: Derive view model from model 👇 | ||
viewModel$ = smosh({ title: this.state.select('title')}, this.sortedSlice$); | ||
|
||
// target API | ||
viewModel$ = smosh({ | ||
prop1: 'prop1', // string | ||
prop2: prop1$ // Observable<string> | ||
}, | ||
slice1$, // Observable<{prop3: 3}> | ||
slice2$ // Observable<{prop4: 'four'}>, | ||
// durationSelector$ (optional) | ||
); | ||
|
||
|
||
// ✔ GOOD: Derive view model from model 👇 | ||
viewModel$ = smosh({ | ||
title: this.state.select('title'), | ||
...this.sortedSlice$ | ||
}); | ||
|
||
|
||
|
||
constructor(private globalState: GlobalState, private state: RxState<Model>) { | ||
// ... | ||
|
||
} | ||
|
||
// ... | ||
} | ||
|
||
``` | ||
|
||
 | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.