如何使用@ngrx/store获取State对象的当前值?

80

在调用Web服务之前,我的服务类需要从状态中获取一个名为 dataForUpdate 的属性。目前,我是这样做的:

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));

}

我正在使用angular2-store-example (https://github.com/ngrx/angular2-store-example/blob/master/src/app/users/models/users.ts)作为指南。
我想知道是否存在更好(更清晰)的方法?
实时示例:https://plnkr.co/edit/WRPfMzPolQsYNGzBUS1g?p=preview
11个回答

87

@ngrx/store v1.x 的原始回答

@ngrx/store 扩展了 BehaviorSubject,并且它有一个可以使用的 value 属性。

this._store.value

这将是你应用的当前状态,然后你可以选择属性、过滤或映射等操作...

更新:

花了一会儿时间才搞清楚你的示例 (: 要获取dataForUpdate的当前值,您可以使用:

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

更新 @ngrx/store v2.x

随着版本升级至2,value已被移除,具体请参阅文档:

已删除了同步地从 Store 中提取最新状态值的API。相反,如果你需要获取状态值,你可以始终依赖于 subscribe() 同步运行:

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

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

    return state;
}

29
你可以始终依赖于subscribe()同步运行。但subscribe()是否同步执行呢?在你的示例中,看起来“state”可能会在执行订阅回调之前返回为未定义。 - Forge_7
3
take() 操作符使得它对于存储可观察对象来说是同步的。在其他可观察对象中,你可能是正确的,理论上它可以是同步或异步的...但这取决于具体情况。在实践中,take() 操作符在处理大多数可观察对象时是同步的。 - Sasxa
3
这个答案似乎不再适用。我得到了一个“在类型Store上不存在take属性”的错误。 - JeffryHouser
12
@JeffryHouser,rjxs 现在要求您使用管道,store.pipe(take(1)).subscribe()。 (注:该代码是使用rxjs库中的pipe函数将Observable进行转换以便只发出第一个值,然后使用subscribe函数订阅Observable。) - PomPom
5
take(1)并不会使它变成同步的,它只是让它发出一次信号,但仍然是异步的。 - sovemp
显示剩余8条评论

30

跟随@Sasxa的答案,新版本的@nrgx/store(v5和v6)语法已经改变。在底层的RxJS库更新到^5.5.0之后,现在所有Observable实例上都有一个pipe方法可用,它允许更容易地进行链接,并改变了订阅的方式。

所以您现在可以这样做:

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;
}

或者,严格使用 pipe() 运算符:

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;
}

如果您想使您的代码更易读,您也可以使用async/await机制,方法如下:

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;
}

11
管道并没有使链式调用更加容易,相反它让代码变得更加复杂和冗长。管道的引入并不是为了让链式调用更容易,而是为了简化树摇优化。顺便说一句,我认为为了满足工具而牺牲开发者的体验来设计API是一个错误。 - Neutrino
3
不,优先考虑开发者体验而不是用户体验是错误的。庞大、未经剪裁的捆绑包并不是一件好事情。 - artparks
1
最好的设计既能实现高效,又能拥有优雅的界面,而不是牺牲其中一个来追求另一个。 - Neutrino
1
已经很久了,但管道也可以避免隐式依赖。 - Michael Pearson

15

withLatestFrom()combineLatest() 方法在订阅链中为您提供所需的内容,并与 Observables+Ngrx 的精神相一致。

使用 withLatestFrom() 替换上面代码中的 GET STATE .mergeMap(),可能会像这样:

...
.withLatestFrom(store$, (payload, state) => { 
    return {payload: payload, stateData: state.data} 
} )
...
作为补充说明,原问题中的代码似乎正在处理redux actions的异步效果,而这正是ngrx/effects库的用途。我建议你去看看。在你安装好Effects之后,管理异步的redux actions的代码会更加简洁易懂。Jim Lynch的这篇文章也对我很有帮助:The Basics of "ngrx/effects", @Effect, and Async Middleware for "ngrx/store" in Angular 2

请注意,从 RxJs 7+ 开始,您可以使用 await + withLatestFrom,例如 'await withLatestFrom(store$)',这很方便,参见 https://dev59.com/a1sX5IYBdhLWcg3wJMnb#63744894。 - XDS

14

虽然不是直接回答问题的答案,但我在寻找如何从存储中检索单个值时找到了这个页面。

为了实现这一点,您可以注入来自@ngrx/storeState对象,如下所示:

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

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

state 对象在私有的 _value 属性中保存当前状态,可以通过 .getValue() 方法进行访问。


4
需要注意的是,在写作时(@ngrx v4.1.1),当使用Redux DevTools并注入“State”时会导致重复记录每个分发的动作。尽管更为简洁,但这可能并不是最实用的路线。 - Alex Currie-Clark

10

我的解决方案


ngStore中的State类是一个BehaviorSubject,所以我们可以注入它,并使用它的value属性来获取最新的值。

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

}

someMethod() {
    // WHAT'S MORE: you can use your selector directly on it!
    let v = yourSelector(this.state.value);
}

5

@ngrx/store v4.x更新

从v4.x开始,我们被迫将take操作符放入管道中以使其变为同步:

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

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

    return state;
}


1

这对我有用。您需要从“@ngrx/store”导入Store,AppState是您的状态。

private state: AppState;

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

ngOnInit() {
    this.store.select(x => this.state = x).subscribe();
}

0

额外的评论。当我使用this._store.value.StateReducer.currentPeriod.id时

编译器返回“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  }; }) ;





}

为了解决问题,我已经在rxjs\subject文件夹中更改了BehaviorSubject.d.ts文件:
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;
}

不确定这是否是合法的修改 ;)


2
更“正式”的写法应该是 constructor (public _store: Store<any>) (: - Sasxa

0
我创建了一个极简应用程序,其中有一个状态,包含两个计数器,它们是AppState的属性,以及两个reducers。每个reducer都绑定到特定的计数器,并为每个计数器订阅了一个observable,将console.log其值。当调用reducers本身时,它们也会写入控制台。
有一个按钮,通过分派事件调用两个reducers。此外,这两个计数器绑定到2个标签,因此对它们的更改会显示- <p>Counter: {{counter1 | async}}</p>
使用StoreModule.forRoot({ counter1: Reducer1, counter2 : Reducer2 })将每个计数器映射到reducer。
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 { }

0

这只是我在处理这个问题时的经验,不是标准的代码

请在 GitHub 上查看答案: State snapshot #227

我想在构造函数中获取state,所以我使用了非同步

constructor (private state: State<AppState>) {

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

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接