How to get current value of State object with @ngrx/store?
Asked Answered
D

12

81

My service class, before calling a web service, needs to get a property called dataForUpdate from my state. Currently, I'm doing it like this:

constructor(public _store: Store < AppState > ,
  public _APIService: APIService) {

  const store$ = this._store.select('StateReducer');

  .../...

  let update = this.actions$.filter(action => action.type == UPDATE)
    .do((action) => this._store.dispatch({
      type: REDUCER_UPDATING,
      payload: action.payload
    })) **
    * GET STATE ** *= => .mergeMap(action => store$.map((state: AppState) => state.dataForUpdate).distinctUntilChanged(),
      (action, dataForUpdate) {
        return {
          type: action.type,
          payload: {
            employee: action.payload,
            dataForUpdate: dataForUpdate
          }
        };
      }) *
    AND CALL API *= => .mergeMap(action => this._APIService.updateEmployee(action.payload.employee, action.payload.dataForUpdate),
      (action, APIResult) => {
        return {
          type: REDUCER_UPDATED
        }
      })
    .share();


  .../...

  let all = Observable.merge(update, ....);
  all.subscribe((action: Action) => this._store.dispatch(action));

}

I'm using angular2-store-example (https://github.com/ngrx/angular2-store-example/blob/master/src/app/users/models/users.ts) as a guide to follow.

I'm wondering if a better (cleaner) way exists?

Live example: https://plnkr.co/edit/WRPfMzPolQsYNGzBUS1g?p=preview

Dropsical answered 25/2, 2016 at 17:3 Comment(0)
F
87

Original answer for @ngrx/store v1.x

@ngrx/store extends BehaviorSubject and it has a value property you can use.

this._store.value

that will be the current state of your app, and from there you can select properties, filter, map etc...

update:

Took me a while to figure what's what in your example (: To get current value of dataForUpdate, you can use:

let x = this._store.value.StateReducer.dataForUpdate;
console.log(x); // => { key: "123" }

Update for @ngrx/store v2.x

With the update to version 2, value was removed as described in docs:

The APIs for synchronously pulling the most recent state value out of Store have been removed. Instead, you can always rely on subscribe() running synchronously if you have to get the state value:

function getState(store: Store<State>): State {
    let state: State;

    store.take(1).subscribe(s => state = s);

    return state;
}
Frigidaire answered 25/2, 2016 at 18:17 Comment(13)
So easy ;) Except that it appear that I must include reducer name . Ex : this._store.value.StateReducer.dataForUpdateDropsical
You made it a bit more complicated than it could be (:Frigidaire
I agree ! That why my feeling was that easier solution must exist. Thank you for your helpDropsical
I cannot get this to work. Property 'value' does not exist on type 'Store<AppStore>'. Does this answer refer to an old version of @ngrx/store perhaps?Judi
@Judi Yea, this answer was for 1.x. They removed this API in 2.0. You'll have to use synchronous .subscirbe()..Frigidaire
"you can always rely on subscribe() running synchronously" Can you? Is subscribe ever synchronous? Seems to me that in your example 'state' could be returned as undefined before the subscribe callback is executed.Oxbow
@Oxbow You can thing of observable chain as series of nested functions, subscribe(take(store(() => {...}))) so they will execute in order before next line. This is of course oversimplified.. watch this video to learn how it works.Frigidaire
@Frigidaire Yes, the call chain can be though of like that, but the (s => state = s) is passing a function and there is no knowing whether it is called back synchronously or asynchronously without knowing what happens when it is called back (inside subscribe). So my new question is this: does the subscribe method in Observable call back the function synchronously or asynchronously? Does it depend on the type of Observable? For example an Observable supplied to us by angular's http library is definitely asynchronous.Oxbow
@Oxbow take() operator makes it synchronous reactivex.io/rxjs/file/es6/operator/take.js.html#lineNumber73 (for store observable), for other observables you might be right, theoretically it could be either sync or async...Frigidaire
This answer no longer seems to work. I'm getting a property take does not exist on type Store.Burn
@JeffryHouser, rjxs force you to pipe now, store.pipe(take(1)).subscribe()Noodlehead
isn't there a risk here that the return will happen before subscribe finishes?Ossa
take(1) does NOT make it synchronous, it just make it emit once but that still happens async.Alek
T
30

Following the answer from @Sasxa, the syntax changed on newer versions of @nrgx/store (v5 and v6). After the underlying RxJS library was updated to ^5.5.0, there is now a pipe method available on all the Observable instances, which allows for easier chaining and changes how a subscription is achieved.

So you can now do something like:

import { take } from 'rxjs/operators';

function getState(store: Store<State>): State {
   let state: State;

   store.select('your-state').pipe(take(1)).subscribe(
      s => state = s
   );

   return state;
}

Or, using strictly the pipe() operator:

import { select } from '@ngrx/store';
import { take } from 'rxjs/operators';

function getState(store: Store<State>): State {
   let state: State;

   store.pipe(select('your-state'), take(1)).subscribe(
      s => state = s
   );

   return state;
}

And if you want to make your code a bit more readable you can also employ async/await mechanics like so:

import { select } from '@ngrx/store';
import { take } from 'rxjs/operators';

function async getStateAsync(store: Store<State>): State {
   let state = await store
             .pipe(
                select('your-state'),
                take(1)
             )
             .toPromise<State>();

   return state;
}
Tape answered 29/9, 2018 at 17:30 Comment(4)
Pipe doesn't making chaning easier, it makes it more difficult and verbose. Pipe was not introduced to make chaining easier but to simplify treeshaking. Fwiw I think it's a mistake to shape api's to satisfy tools at the expense of programmers but w/e.Valenevalenka
no, it's a mistake to prioritise developer experience over user experience. huge, un tree-shaken bundles are not a good thing.Curable
The best designs achieve efficient implementation and elegant interfaces, not one at the expense of the other.Valenevalenka
It's been forever, but pipes also avoid implicit dependencies.Selfknowledge
G
15

withLatestFrom() or combineLatest() methods in the subscription chain give you just what you need, and are aligned with the spirit of Observables+Ngrx.

In place of the GET STATE .mergeMap() in the code above, using withLatestFrom() would look something like this:

...
.withLatestFrom(store$, (payload, state) => { 
    return {payload: payload, stateData: state.data} 
} )
...

As an aside, the code in the original question appears to be managing asynchronous effects of redux actions, which is exactly what the ngrx/effects library is for. I suggest you check it out. After you get Effects wired up, the code for managing asynchronous redux actions is much cleaner. This article by Jim Lynch was also super helpful to me: The Basics of "ngrx/effects", @Effect, and Async Middleware for "ngrx/store" in Angular 2

Glindaglinka answered 27/3, 2017 at 22:46 Comment(1)
Note that as of RxJs 7+ you can use await + withLatestFrom like so 'await withLatestFrom(store$)' which is neat per https://mcmap.net/q/45851/-how-can-i-await-on-an-rx-observableYester
D
14

Not strictly a direct answer to the question, but I found this page looking for how to retrieve a single value from the store.

To achieve this, you can inject the State object from @ngrx/store as shown below:

import { State } from '@ngrx/store';

constructor (private state: State<AppState>) {
    let propertyValue = state.getValue().path.to.state.property;
}

The state object holds the current state in a private _value property, accessed by the .getValue() method.

Digenesis answered 3/12, 2017 at 12:36 Comment(1)
It should be noted that at the time of writing (@ngrx v4.1.1) injecting the State causes issues when used with Redux DevTools, repeating every action dispatched. Perhaps not the most practical of routes after all, despite being more succinct.Digenesis
T
10

My Solution


the State class in ngStore is an BehaviorSubject, so we can inject it, and use it's value property to get the latest value.

constructor(private state:State<YourState>...) {

}

someMethod() {
    // WHAT'S MORE: you can use your selector directly on it!
    let v = yourSelector(this.state.value);
}
Transplant answered 8/6, 2018 at 2:20 Comment(0)
H
5

Update for @ngrx/store v4.x

As of v4.x we are forced to put the take operator into the pipe like that to get it synchronous:

function getState(store: Store<State>): State {
    let state: State;

    store.pipe(take(1)).subscribe(s => state = s);

    return state;
}

Hobie answered 11/5, 2020 at 8:9 Comment(0)
L
1

That's works for me. You need to import Store from '@ngrx/store' and AppState is your state.

private state: AppState;

constructor(private store: Store<AppState>) { }

ngOnInit() {
    this.store.select(x => this.state = x).subscribe();
}
Leo answered 13/11, 2019 at 11:26 Comment(0)
D
0

Extra comment. When I use this._store.value.StateReducer.currentPeriod.id

Transpiler return "app/state/stateService.ts(133,35): error TS2339: Property 'StateReducer' does not exist on type 'AppState'."

constructor ( public _store: Store<AppState>) {

    const store$ =  this._store.select ('StateReducer');

    .../...


    let saveTransaction = this.actions$
                  .filter (action => action.type==SAVE_TRANSACTION )
                  .map (action => { return { type:SAVING_TRANSACTION, payload : action.payload };  } )
                  .mergeMap ( action => this._transactionService.updateTransaction (
                                                            this._store.value.StateReducer.currentProfile.id, 
                                                            this._store.value.StateReducer.currentPeriod.id, 
                                                            action.payload),
                                (state, webServiceResponse) =>  { return { type:TRANSACTION_UPDATED, payload :null  }; }) ;





}

To fix issue, I have changed BehaviorSubject.d.ts in rxjs\subject folder :

import { Subject } from '../Subject';
import { Subscriber } from '../Subscriber';
import { Subscription } from '../Subscription';
export declare class BehaviorSubject<T> extends Subject<T> {
    private _value;
    private _hasError;
    private _err;
    constructor(_value: T);
    getValue(): T;        
    value: T;             <=== I have changed it to value: any;
    _subscribe(subscriber: Subscriber<any>): Subscription<T>;
    _next(value: T): void;
    _error(err: any): void;
}

Not sure if it's a legit modification ;)

Dropsical answered 25/2, 2016 at 18:58 Comment(1)
More "legit" would be constructor (public _store: Store<any>) (:Frigidaire
A
0

I've created a minimalistic application that has a state with 2 counters which are properties of the AppState, and 2 reducers. Each reducer is bound to a particular counter, and I've subscribed an observable for each counter that will console.log its value. The reducers themselves also write to the console when called.

There is a button which calls both reducers by dispatching an event. Also, the 2 counters are bound to 2 labels, so changes in them show - <p>Counter: {{counter1 | async}}</p>.

Mapping each counter to a reducer is done with StoreModule.forRoot({ counter1: Reducer1, counter2 : Reducer2 })

import { Component, NgModule } from '@angular/core';
import { Store, Action, StoreModule } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { BrowserModule } from '@angular/platform-browser';

interface AppState {
  counter1 : number;
  counter2 : number;
}

export function Reducer1(counter : number = 0, action : Action) {
  console.log(`Called Reducer1: counter=${counter}`);
  return counter + 1;
}

export function Reducer2(counter : number = 0, action : Action) {
  console.log(`Called Reducer2: counter=${counter}`);
  return counter + 2;
}

@Component({
  selector: 'app-root',
  template: `<p>Counter: {{counter1 | async}}</p>
  <p>Counter: {{counter2 | async}}</p>
  <button (click)='increment()'>Increment</button>`
})
export class AppComponent {
  title = 'app';
  counter1 : Observable<number>;
  counter2 : Observable<number>;

  constructor(private store : Store<AppState>) {
    this.counter1 = this.store.select('counter1');
    this.counter2 = this.store.select('counter2');

    this.counter1.subscribe(x => console.log(`Subscribe event for counter1 fired: counter=${x}`));
    this.counter2.subscribe(x => console.log(`Subscribe event for counter2 fired: counter=${x}`));
  }

  increment() {
    this.store.dispatch({type:'foo'});
  }
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ counter1: Reducer1, counter2 : Reducer2 })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
Attune answered 14/2, 2018 at 10:1 Comment(0)
L
0

This only my exprience with this problem and not a standard code.

please see the answer in github: State snapshot #227

i want to get state in constractor so i use to unasynchronous:

constructor (private state: State<AppState>) {

   this.store.select("yourSelector").forEach(yourSelector => { 
       this.property = yourSelector.path.to.state.property 
   });

}
Labradorite answered 19/2, 2021 at 6:46 Comment(0)
C
0

A hacky way to do this is to wrap the subscription in a promise, then await the promise.

import { Store } from '@ngrx/store';

// Assume this function is defined in some common location.
// Pass in a reference to the store and the desired selector.
export const getState = (store, selector) => {
  return new Promise((resolve, reject) => {
    // Get an observable from the selector.
    const someState$ = store.select(selector);

    // Set up the subscription and resolve its callback value.
    // This approach to defining the subscription lets you wire up
    // both resolve and reject handling.
    someState$.subscribe({
      next: value => {
        // Unsubscribe to prevent memory leaks
        someState$.unsubscribe();
        
        // Resolve the promise with the subscribed value
        resolve(value);
      },
      error: err => {
        // Unsubscribe and reject the promise in case of an error
        someState$.unsubscribe();
        reject(err);
      }
    });
  });
}

// ...

// Assume this is called elsewhere, such as from your angular component,
// for example.
async doSomething() {
  try {
    const result = await getData(this.store, myFeature.selectFoo);
    console.log('Synchronous result:', result);
  } catch (error) {
    console.error('An error occurred', error);
  }
}

Be cautious when doing this though. Ngrx was designed to work reactively and its integration with Rxjs observables power this functionality. Where possible, prefer using observables or the | async pipe in your component template to ensure your application remains as reactive as possible.

That said, there are likely other edge cases where subscriptions aren't ideal. In that case, writing some generic functionality to select state synchronously can be done in an abstract way that can be reused as a utility where you need it in your application.

Corr answered 20/12, 2023 at 11:12 Comment(0)
P
-2

this is work for me. i will get my object data.

this.store.select('dataStore').subscribe(data => { 
          console.log(data)
 }
Piercing answered 9/8, 2018 at 6:26 Comment(2)
it will get called everytime the select changes, you should do unsubscribe somewhere cuz it is not a complete ObservableBellbottoms
Actually, I just needed something quick and this did the trick.Applicative

© 2022 - 2025 — McMap. All rights reserved.