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

Skip to content

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
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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 Feb 3, 2022
a5a0e49
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
78becbd
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
caed2fa
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
70652ab
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
d622605
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
ef3131f
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
d2d52ff
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 3, 2022
0352d14
perf: reduce smosh, organize typings
BioPhoton Feb 7, 2022
f0401df
Merge branch 'main' into reintro-accumulate-observable
BioPhoton Feb 12, 2022
f4bdba7
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
e09314d
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
6da1e2e
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
ca28c9e
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
406cece
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
4b2d18e
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
ba7cb50
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
041f90d
Merge branch 'main' into reintro-accumulate-observable
BioPhoton Feb 27, 2022
b7a02bd
Update libs/state/selections/docs/Readme.md
BioPhoton Feb 27, 2022
163e81b
Update Readme.md
BioPhoton Feb 27, 2022
119328b
Update Readme.md
BioPhoton Mar 7, 2022
3893bb5
Update Readme.md
BioPhoton Mar 7, 2022
910331e
refactor(state): wip selections add spreads to smosh
BioPhoton Mar 7, 2022
7406ec6
refactor(state): wip selections add spreads to smosh
BioPhoton Mar 8, 2022
94d8b02
Merge branch 'main' into reintro-accumulate-observable
BioPhoton Mar 9, 2022
7d474c5
docs(state): selection
BioPhoton Mar 23, 2022
d8f4e5f
Update libs/state/selections/docs/Readme.md
BioPhoton Mar 24, 2022
d8f2ae1
Update libs/state/selections/docs/Readme.md
BioPhoton Mar 24, 2022
0b1bda8
chore(state): make eslint happy
edbzn Mar 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/state/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RxState is a lightweight, flexible, strongly typed and tested tool dedicated to

## Sub Modules

- [**{ }** State (root module)](https://github.com/rx-angular/rx-angular/blob/main/libs/state/README.md)
- [🧩 Selections](https://github.com/rx-angular/rx-angular/blob/main/libs/state/selections/README.md)
- [☁ Effects](https://github.com/rx-angular/rx-angular/blob/main/libs/state/effects/README.md)

Expand Down
15 changes: 11 additions & 4 deletions libs/state/selections/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,23 @@

## Slogan

`@rx-angular/state/selections` TBD
`@rx-angular/state/selections` provides performant and boiler plate free helpers for to craft custom selections with just a few lines of code.

## Key features

- βœ…
- βœ… reduces repetitative code to a minimum
- βœ… most common problems solved with a 1 liner
- βœ… distinct values
- βœ… lazy initialization (avoids `?` in the template)
- βœ… shared computation
- βœ… strongly types
- βœ… fully tested



## Demos:

- ⚑ GitHub
- ⚑ GitHub Todo

## Install

Expand All @@ -27,4 +35,3 @@ yarn add @rx-angular/state
## Documentation

- [Selections](https://github.com/rx-angular/rx-angular/tree/main/libs/state/selections/docs/Readme.md)

278 changes: 272 additions & 6 deletions libs/state/selections/docs/Readme.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,276 @@
# Motivation

TBD
![Selections (1)](https://user-images.githubusercontent.com/10064416/152422745-b3d8e094-d0f0-4810-b1b2-5f81fae25938.png)

* [`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.

![Selections (2)](https://user-images.githubusercontent.com/10064416/152422803-bfd07ab2-0a6f-4521-836e-b71677e11923.png)

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

![Selections (3)](https://user-images.githubusercontent.com/10064416/152422856-a483a06c-84e0-4067-9eaa-f3bb54a0156d.png)


- 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
- Strongly typed

# Concepts

## Selection composition - lazy vs eager

## Selection composition - functional vs reactive

## Selection setup - 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)

![Selections (5)](https://user-images.githubusercontent.com/10064416/152422955-cb89d198-1a69-450b-be84-29dd6c8c4fdb.png)


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.

![Selections (4)](https://user-images.githubusercontent.com/10064416/152422883-0b5f6006-7929-4520-b0b2-79eb61e4eb08.png)

# Usage

## select

`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.

```typescritp
// emissions:
// 0. - no emission ever happened
// 1. {a: 1} - incomplete state leads to `?` pollution in the template
// 2. {a: 1, b: 'a'} - render relevant emission
// 2. {a: 1, b: 'a'} - same instance emisssion
// 3. {a: 1, b: 'a', c: true} - render irrelevant change
// 4. {a: 1, b: 'b', c: true} - render relevant emission
const model$: Observable<Partial<{a: number, b: string, c: boolean}>>;
```
**Problem**
```html
<!--

Computes 2 times & Renders 0. ❌; 1. ❌; 2. βœ…; 3. ❌; .4 βœ…
-->
<div *rxLet="model$; let vm">
B: {{vm?.b}}
</div>
B: {{(model$ | push)?.b}}
```

### single property short hand
```typescritp
const vm$ = model$.pipe(select('b'));
```
```html
<!--
Computes 1 time & Renders 2. βœ…; .4 βœ…
-->
<div *rxLet="model$; let vm">
B: {{vm.b}}
</div>
B: {{(model$ | push).b}}
```

### single operators
```typescritp
const vm$: Observable<> = model$.pipe(select(map(({b}) => b === 'a')));
```
```html
<!--
Computes 1 time & Renders 2. βœ…; .4 βœ…
-->
<div *rxLet="model$; let vm">
B: {{vm.b}}
</div>
B: {{(model$ | push).b}}
```

## selectSlice

## smosh

## distinctUntilSomeChanges

# 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$);
}

// ...
}

```

![Selections (6)](https://user-images.githubusercontent.com/10064416/152422999-db8260f0-69e1-4d99-b6ac-b2b1d043b4b7.png)

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>) {
// ...

}

// ...
}

```

![Selections (7)](https://user-images.githubusercontent.com/10064416/152423026-d23326c2-97d5-4bd0-9015-f498c3fc0e55.png)

Loading