Skip to content

Commit

Permalink
refactor: add internal implementation of toSignal for State observable
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonroberts committed Apr 25, 2023
1 parent 513b8ea commit 0ed9f47
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 9 deletions.
4 changes: 2 additions & 2 deletions modules/store/spec/integration_signals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ describe('NgRx and Signals Integration spec', () => {
});

describe('context integration spec', () => {
it('Store.selectSignal should throw an error if used outside in the injection context', () => {
it('Store.selectSignal should not throw an error if used outside in the injection context', () => {
let error;

try {
Expand All @@ -127,7 +127,7 @@ describe('NgRx and Signals Integration spec', () => {
error = `${e}`;
}

expect(error).toContain('Error: NG0203');
expect(error).toBeUndefined();
});
});
});
12 changes: 7 additions & 5 deletions modules/store/src/store.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
// disabled because we have lowercase generics for `select`
import { Injectable, Provider, Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { computed, Injectable, Provider, Signal } from '@angular/core';
import { Observable, Observer, Operator } from 'rxjs';
import { distinctUntilChanged, map, pluck } from 'rxjs/operators';

import { ActionsSubject } from './actions_subject';
import { Action, ActionReducer, FunctionIsNotAllowed } from './models';
import { ReducerManager } from './reducer_manager';
import { StateObservable } from './state';
import { toSignal } from './to_signal';

@Injectable()
export class Store<T = object>
extends Observable<T>
implements Observer<Action>
{
private readonly state: Signal<T>;

constructor(
state$: StateObservable,
private actionsObserver: ActionsSubject,
Expand All @@ -22,6 +24,7 @@ export class Store<T = object>
super();

this.source = state$;
this.state = toSignal(state$);
}

select<K>(mapFn: (state: T) => K): Observable<K>;
Expand Down Expand Up @@ -100,9 +103,8 @@ export class Store<T = object>
*
* @param selectorFn selector function
*/
selectSignal<K>(selectorFn: (state: T) => K): Signal<K>;
selectSignal<K = unknown>(selectorFn: (state: T) => K): Signal<unknown> {
return toSignal(this.select(selectorFn), { requireSync: true });
selectSignal<K>(selector: (state: T) => K): Signal<K> {
return computed(() => selector(this.state()));
}

override lift<R>(operator: Operator<T, R>): Store<R> {
Expand Down
28 changes: 28 additions & 0 deletions modules/store/src/to_signal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { signal, Signal } from '@angular/core';
import { StateObservable } from './state';

/**
* Get the current value of an `StateObservable` as a reactive `Signal`.
*
* `toSignal` returns a `Signal` which provides synchronous reactive access to values produced
* by the `StateObservable`, by subscribing to that `StateObservable`. The returned `Signal` will always
* have the most recent value emitted by the subscription, and will throw an error if the
* `StateObservable` errors.
*
* The subscription will last for the lifetime of the application itself.
*
* This function is for internal use only as it differs from the `toSignal`
* provided by the `@angular/core/rxjs-interop` package with relying on
* the injection context to unsubscribe from the provided observable.
*
*/
export function toSignal<T, U>(source: StateObservable): Signal<T | U> {
let state = signal<T | U>(undefined as T);

source.subscribe({
next: (value) => state.set(value),
error: (error) => state.set(error),
});

return state.asReadonly();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { LoginPageActions } from '@example-app/auth/actions';
template: `
<bc-login-form
(submitted)="onSubmit($event)"
[pending]="pending()!"
[pending]="pending()"
[errorMessage]="error()"
>
</bc-login-form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { LayoutActions } from '@example-app/core/actions';
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<bc-layout>
<bc-sidenav [open]="showSidenav()!" (closeMenu)="closeSidenav()">
<bc-sidenav [open]="showSidenav()" (closeMenu)="closeSidenav()">
<bc-nav-item
(navigate)="closeSidenav()"
*ngIf="loggedIn()"
Expand Down

0 comments on commit 0ed9f47

Please sign in to comment.