Skip to content

Commit

Permalink
fix(store): move Angular Signal interop into State service (#3879)
Browse files Browse the repository at this point in the history
Closes #3869
  • Loading branch information
brandonroberts authored May 8, 2023
1 parent 0aa1582 commit 8cb5795
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 9 deletions.
4 changes: 2 additions & 2 deletions modules/data/spec/selectors/entity-selectors$.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Action, MemoizedSelector, Store } from '@ngrx/store';
import { Action, MemoizedSelector, StateObservable, Store } from '@ngrx/store';

import { BehaviorSubject, Observable, Subject } from 'rxjs';
import {
Expand Down Expand Up @@ -75,7 +75,7 @@ describe('EntitySelectors$', () => {
actions$ = new Subject<Action>();
state$ = new BehaviorSubject({ entityCache: emptyCache });
store = new Store<{ entityCache: EntityCache }>(
state$,
state$ as unknown as StateObservable,
null as any,
null as any
);
Expand Down
42 changes: 42 additions & 0 deletions modules/store/spec/store_pipes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TestBed } from '@angular/core/testing';
import { Store, provideStore } from '@ngrx/store';
import { Component, inject, Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'test', standalone: true })
export class TestPipe implements PipeTransform {
store = inject(Store);

transform(s: number) {
this.store.select('count');
return s * 2;
}
}

@Component({
selector: 'test-component',
standalone: true,
imports: [TestPipe],
template: `{{ 3 | test }}`,
})
export class TestComponent {}

describe('NgRx Store Integration', () => {
describe('with pipes', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestComponent],
providers: [
provideStore({ count: () => 2 }, { initialState: { count: 2 } }),
],
});
});

it('should not throw an error', () => {
const component = TestBed.createComponent(TestComponent);

component.detectChanges();

expect(component.nativeElement.textContent).toBe('6');
});
});
});
17 changes: 15 additions & 2 deletions modules/store/src/state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Inject, Injectable, OnDestroy, Provider } from '@angular/core';
import { Inject, Injectable, OnDestroy, Provider, Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import {
BehaviorSubject,
Observable,
Expand All @@ -13,14 +14,24 @@ import { ReducerObservable } from './reducer_manager';
import { ScannedActionsSubject } from './scanned_actions_subject';
import { INITIAL_STATE } from './tokens';

export abstract class StateObservable extends Observable<any> {}
export abstract class StateObservable extends Observable<any> {
/**
* @internal
*/
abstract readonly state: Signal<any>;
}

@Injectable()
export class State<T> extends BehaviorSubject<any> implements OnDestroy {
static readonly INIT = INIT;

private stateSubscription: Subscription;

/**
* @internal
*/
public state: Signal<T>;

constructor(
actions$: ActionsSubject,
reducer$: ReducerObservable,
Expand Down Expand Up @@ -50,6 +61,8 @@ export class State<T> extends BehaviorSubject<any> implements OnDestroy {
this.next(state);
scannedActions.next(action as Action);
});

this.state = toSignal(this, { manualCleanup: true, requireSync: true });
}

ngOnDestroy() {
Expand Down
8 changes: 5 additions & 3 deletions modules/store/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import { computed, Injectable, Provider, Signal } from '@angular/core';
import { Observable, Observer, Operator } from 'rxjs';
import { distinctUntilChanged, map, pluck } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop';

import { ActionsSubject } from './actions_subject';
import {
Expand All @@ -19,7 +18,10 @@ export class Store<T = object>
extends Observable<T>
implements Observer<Action>
{
private readonly state: Signal<T>;
/**
* @internal
*/
readonly state: Signal<T>;

constructor(
state$: StateObservable,
Expand All @@ -29,7 +31,7 @@ export class Store<T = object>
super();

this.source = state$;
this.state = toSignal(state$, { manualCleanup: true });
this.state = state$.state;
}

select<K>(mapFn: (state: T) => K): Observable<K>;
Expand Down
10 changes: 9 additions & 1 deletion modules/store/testing/src/mock_state.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import { Injectable } from '@angular/core';
import { Injectable, Signal } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class MockState<T> extends BehaviorSubject<T> {
/**
* @internal
*/
readonly state: Signal<T>;

constructor() {
super(<T>{});

this.state = toSignal(this, { manualCleanup: true, requireSync: true });
}
}
2 changes: 2 additions & 0 deletions projects/standalone-app/src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { RouterTestingModule } from '@angular/router/testing';
import { provideMockStore } from '@ngrx/store/testing';

describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [RouterTestingModule, AppComponent],
providers: [provideMockStore()],
}).compileComponents();
});

Expand Down
5 changes: 4 additions & 1 deletion projects/standalone-app/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ import {
INITIAL_STATE_TOKEN,
provideComponentStore,
} from '@ngrx/component-store';
import { TestPipe } from './test.pipe';

@Component({
selector: 'ngrx-root',
standalone: true,
imports: [RouterModule],
imports: [RouterModule, TestPipe],
template: `
<h1>Welcome {{ title }} {{ val() }}</h1>
<a routerLink="/feature">Load Feature</a>
{{ 3 | test }}
<router-outlet></router-outlet>
`,
providers: [
Expand Down
11 changes: 11 additions & 0 deletions projects/standalone-app/src/app/test.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { Store } from '@ngrx/store';

@Pipe({ name: 'test', standalone: true })
export class TestPipe implements PipeTransform {
store = inject(Store);
transform(s: number) {
this.store.select('count');
return s * 2;
}
}

0 comments on commit 8cb5795

Please sign in to comment.