Skip to content

Commit

Permalink
feat(core): factories for factory selectors for ngrx 12+ #694
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Jul 11, 2021
1 parent 326d6b2 commit 904d99a
Show file tree
Hide file tree
Showing 20 changed files with 270 additions and 46 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ dist
node_modules
test-reports
tmp

*.iml
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit $1
4 changes: 4 additions & 0 deletions .husky/post-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

git update-index --again
6 changes: 6 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
npm run lint
npm run test
34 changes: 34 additions & 0 deletions docs/articles/api/core/to-factory-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
title: toFactorySelector
description: Information about toFactorySelector function and how to create a factory selector function
sidebar_label: toFactorySelector
---

`toFactorySelector` helps to create a factory selector function from a root selector.
In this case, the produced selector can be passed directly to NGRX `store.select`.

This function is useful for NGRX v12 and younger.

```ts
export class MyComponent {
public readonly users$: Observable<User>;

private readonly selectUser =
toFactorySelector(
rootUser(
relUserCompany(
relCompanyAddress(),
),
),
);

constructor(private store: Store) {
this.users$ = this.store.select(
// let's select current user
this.selectUser(
selectCurrentUserId,
),
);
}
}
```
33 changes: 33 additions & 0 deletions docs/articles/api/core/to-static-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: toStaticSelector
description: Information about toStaticSelector function and how to bind param to a root selector
sidebar_label: toStaticSelector
---

`toStaticSelector` helps to create a selector function from a root selector.
Its behavior very similar to [`toFactorySelector`](./to-factory-selector.md),
with the difference that the passed params are static and cannot be changed.

This function is useful for NGRX v12 and younger.

```ts
export class MyComponent {
public readonly users$: Observable<User>;

private readonly selectCurrentUser =
toStaticSelector(
rootUser(
relUserCompany(
relCompanyAddress(),
),
),
selectCurrentUserId,
);

constructor(private store: Store) {
this.users$ = this.store.select(
this.selectCurrentUser,
);
}
}
```
12 changes: 8 additions & 4 deletions docs/articles/guide/ngrx-data.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,16 @@ export class MyComponent {
userSelectors: UserSelectorService,
) {
this.user$ = store.select(
userSelectors.selectUser,
'1',
toStaticSelector(
userSelectors.selectUser,
'1',
),
);
this.users$ = store.select(
userSelectors.selectUsers,
['1', '2'],
toStaticSelector(
userSelectors.selectUsers,
['1', '2'],
),
);
}
}
Expand Down
67 changes: 51 additions & 16 deletions docs/articles/guide/quick.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ the only requirement is the `Dictionary` (a regular object).
The next step is to define functions which select the state of an entity.
In this library, they are called [**entity state selectors**](../api/core/entity-state-selector.md).

```ts
// Redux
```ts title="Redux"
export const selectUserState = state => state.users;
export const selectCompanyState = state => state.companies;
// `stateKeys` function helps in case of different names of the properties.
Expand All @@ -67,8 +66,7 @@ export const selectAddressState = stateKeys(
);
```

```ts
// NGRX
```ts title="NGRX"
export const selectUserState =
createFeatureSelector<fromUser.State>(
'users',
Expand Down Expand Up @@ -165,8 +163,7 @@ In case of arrays, such as `company.staff`, there is [`childrenEntitiesSelector`
Now, let's go to a component where we want to select a user with relationships,
and create a **root selector** via the factories there:

```ts
// Redux
```ts title="Redux"
const selectUser = rootUser(
relUserCompany(
relCompanyAddress(),
Expand All @@ -182,8 +179,32 @@ const mapStateToProps = state => {
export default connect(mapStateToProps)(MyComponent);
```

```ts
// NGRX
```ts title="NGRX 12 and younger"
export class MyComponent {
public readonly users$: Observable<User>;

private readonly selectUser =
rootUser(
relUserCompany(
relCompanyAddress(),
),
);

constructor(private store: Store) {
this.users$ = this.store.select(
// toStaticSelector should be used
// to create a selector for v12
toStaticSelector(
this.selectUser,
// '1' is the id of user
'1',
),
);
}
}
```

```ts title="NGRX 11 and older"
export class MyComponent {
public readonly users$: Observable<User>;

Expand All @@ -205,13 +226,20 @@ export class MyComponent {

Of course, instead of a hardcoded id like `1`, we can pass another **selector, that selects ids** from the state.

```ts
// Redux
```ts title="Redux"
selectUser(state, selectCurrentUserId);
```

```ts
// NGRX
```ts title="NGRX 12 and younger"
this.store.select(
toStaticSelector(
this.selectUser,
selectCurrentUserId,
),
);
```

```ts title="NGRX 11 and older"
this.store.select(this.selectUser, selectCurrentUserId);
```

Expand All @@ -234,13 +262,20 @@ const selectUsers = rootEntities(selectUser);

Now we can use `selectUsers` in our components, but instead of an id, it requires an array of them.

```ts
// Redux
```ts title="Redux"
selectUsers(state, ['1', '2']);
```

```ts
// NGRX
```ts title="NGRX 12 and younger"
this.store.select(
toStaticSelector(
this.selectUsers,
['1', '2'],
),
);
```

```ts title="NGRX and older"
this.store.select(this.selectUsers, ['1', '2']);
```

Expand Down
5 changes: 3 additions & 2 deletions docs/articles/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ The current version of `ngrx-entity-relationship` has been tested and can be use

- Redux 4, React Redux 7, **try it live on [StackBlitz](https://stackblitz.com/edit/ngrx-entity-relationship-react?file=src/MyComponent.tsx)
or [CodeSandbox](https://codesandbox.io/s/github/satanTime/ngrx-entity-relationship-react?file=/src/MyComponent.tsx)**

- NGRX 10, **try it live on [StackBlitz](https://stackblitz.com/github/satanTime/ngrx-entity-relationship-angular?file=src/app/app.component.ts)
- NGRX 12, **try it live on [StackBlitz](https://stackblitz.com/github/satanTime/ngrx-entity-relationship-angular?file=src/app/app.component.ts)
or [CodeSandbox](https://codesandbox.io/s/github/satanTime/ngrx-entity-relationship-angular?file=/src/app/app.component.ts)**
- NGRX 11
- NGRX 10
- NGRX 9
- NGRX 8
- NGRX 7
Expand Down
2 changes: 2 additions & 0 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ module.exports = {
'api/core/childentityselector-function',
'api/core/childrenentities-function',
'api/core/childrenentitiesselector-function',
'api/core/to-factory-selector',
'api/core/to-static-selector',
],
},
{
Expand Down
18 changes: 9 additions & 9 deletions e2e/a12/src/app/entity/entity.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {ChangeDetectionStrategy, Component, OnDestroy} from '@angular/core';
import {select, Store} from '@ngrx/store';
import {rootEntities} from 'ngrx-entity-relationship';
import {rootEntities, toFactorySelector} from 'ngrx-entity-relationship';
import {combineLatest, Observable} from 'rxjs';
import {filter, map, switchMap} from 'rxjs/operators';
import {Company} from './store/company/company.model';
Expand Down Expand Up @@ -29,7 +29,7 @@ import {User} from './store/user/user.model';
export class EntityComponent implements OnDestroy {
public readonly company$: Observable<Company | undefined>;
// prettier-ignore
private readonly companyWithCrazyData = rootCompany(
private readonly companyWithCrazyData = toFactorySelector(rootCompany(
relCompanyAddress(),
relCompanyAdmin(
relUserEmployees(),
Expand All @@ -41,36 +41,36 @@ export class EntityComponent implements OnDestroy {
),
),
),
);
));

public readonly users$: Observable<Array<User>>;
// prettier-ignore
private readonly users = rootEntities(
private readonly users = toFactorySelector(rootEntities(
rootUser(
relUserEmployees(
relUserManager(),
),
relUserManager(),
),
);
));

constructor(protected readonly store: Store<State>, public readonly entitiesService: EntityService) {
this.users$ = combineLatest([
this.store.select(this.users, selectCurrentUsersIds),
this.store.select(this.users(selectCurrentUsersIds)),
this.store.pipe(
select(selectCurrentUsersIds),
switchMap(ids => this.store.select(this.users, ids)),
switchMap(ids => this.store.select(this.users(ids))),
),
]).pipe(
filter(([a, b]) => a === b),
map(([a]) => a),
);

this.company$ = combineLatest([
this.store.select(this.companyWithCrazyData, selectCurrentCompanyId),
this.store.select(this.companyWithCrazyData(selectCurrentCompanyId)),
this.store.pipe(
select(selectCurrentCompanyId),
switchMap(id => this.store.select(this.companyWithCrazyData, id)),
switchMap(id => this.store.select(this.companyWithCrazyData(id))),
),
]).pipe(
filter(([a, b]) => a === b),
Expand Down
7 changes: 4 additions & 3 deletions e2e/a12/src/app/entity/store/entity.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {Injectable} from '@angular/core';
import {Store} from '@ngrx/store';
import {toStaticSelector} from 'ngrx-entity-relationship';
import {filter, first, tap} from 'rxjs/operators';
import {UpdateAddress} from './address/address.actions';
import {Address} from './address/address.model';
Expand All @@ -15,7 +16,7 @@ export class EntityService {

public changeUser(id: string): void {
this.store
.select(rootUser(), id)
.select(toStaticSelector(rootUser(), id))
.pipe(
filter((v): v is User => !!v),
first(),
Expand All @@ -40,7 +41,7 @@ export class EntityService {

public changeCompany(id: string): void {
this.store
.select(rootCompany(), id)
.select(toStaticSelector(rootCompany(), id))
.pipe(
filter((v): v is Company => !!v),
first(),
Expand All @@ -65,7 +66,7 @@ export class EntityService {

public changeAddress(id: string): void {
this.store
.select(rootAddress(), id)
.select(toStaticSelector(rootAddress(), id))
.pipe(
filter((v): v is Address => !!v),
first(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {HANDLER_ROOT_ENTITIES, HANDLER_ROOT_ENTITY, ID_TYPES} from 'ngrx-entity-relationship';
import {HANDLER_ROOT_ENTITIES, HANDLER_ROOT_ENTITY, ID_TYPES, toFactorySelector} from 'ngrx-entity-relationship';
import {iif, Observable, of} from 'rxjs';
import {map, switchMap} from 'rxjs/operators';

export interface STORE_INSTANCE<T> {
select<K, Props>(mapFn: (state: T, props: Props) => K, props: Props): Observable<K>;
select<K>(mapFn: (state: T) => K): Observable<K>;
}

export function relationships<STORE, ENTITY>(
Expand All @@ -30,6 +30,8 @@ export function relationships<STORE, SET, TRANSFORMED, TYPES>(
store: STORE_INSTANCE<STORE>,
selector: HANDLER_ROOT_ENTITY<STORE, SET, SET | TRANSFORMED, TYPES>,
): (next: Observable<SET>) => Observable<undefined | SET | TRANSFORMED> {
const factory = toFactorySelector(selector);

return next =>
next.pipe(
switchMap(input => {
Expand All @@ -48,7 +50,7 @@ export function relationships<STORE, SET, TRANSFORMED, TYPES>(
}
return selector.idSelector(set) as any as TYPES;
}),
switchMap(id => store.select(selector, id)),
switchMap(id => store.select(factory(id))),
),
);
}),
Expand Down

0 comments on commit 904d99a

Please sign in to comment.