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

Skip to content

docs: add selection docs #1498

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 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/docs/docs/state/actions/actions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ hide_title: true

- βœ… Fully Typed
- βœ… No-Boilerplate
- βœ… Configurable transformations to have lines in the template
- βœ… Configurable transformations to have less lines in the template
- βœ… Minimal memory footprint through a Proxy object and lazy initialization

## Demos:
Expand Down
263 changes: 249 additions & 14 deletions apps/docs/docs/state/selections/selections.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ hide_title: true

## Key features

- βœ…

## Demos:

- ⚑ GitHub
- βœ… Fully Typed
- βœ… No-Boilerplate
- βœ… Performant selections
- βœ… Minimal memory footprint through a Proxy object and lazy initialization

## Install

Expand All @@ -28,16 +27,252 @@ npm install --save @rx-angular/state
yarn add @rx-angular/state
```

## Documentation
# Motivation

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

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

```typescript
// 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 emission
// 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

```typescript
const vm$ = model$.pipe(select('b'));
```

```html
<!--
Computes 1 time & Renders 2. βœ…; .4 βœ…
-->
<div *rxLet="vm$; let b">B: {{ b }}</div>
B: {{(vm$ | push)}}
```

### single operators

```typescript
const vm$: Observable<> = model$.pipe(select(map(({ b }) => b === 'a')));
```

```html
<!--
Computes 1 time & Renders 2. βœ…; .4 βœ…
-->
<div *rxLet="vm$; let vm">B: {{vm.b}}</div>
B: {{(vm$ | push).b}}
```

## selectSlice

## distinctUntilSomeChanges

## stateful

# 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 computed in the component class - `string`
- the sort direction triggered over a UI element click - `boolean`

A setup of the components 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
export class ProblemComponent {
private sortedList$ = this.state.select(
selectSlice(['sortDirection', 'list']),
map(() => {
// sort `list` by `sortDirection` to `sortedList` here
return sortedList;
})
);

- [Selections](https://rx-angular.io/docs/state/selections)
viewModel$ = this.state.select(
selectSlice(['title', 'sortedList', 'sortDirection'])
);

## Motivation
// ❌ BAD: model and view model mix up πŸ‘‡
constructor(
private globalState: GlobalState,
private state: RxState<Model & Pick<ViewModel, 'sortedList'>>
) {
// ...

TBD
// ❌ 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
export class ProblemComponent {
private sortedSlice$ = this.state.select(
['sortDirection', 'list'],
({ list, sortDirection }) => {
// sort `list` by `sortDirection` to `sortedList` here
return { sortDirection, sortedList };
}
);

// βœ” GOOD: Derive view model from model πŸ‘‡
vm$ = // what to promote here as smosh isn't part of RxA?

constructor(private globalState: GlobalState, private state: RxState<Model>) {
// ...
}

// ...
}
```

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