ngRx/store 可观察值未显示在 Angular 模板中

ngRx/store Observable value not showing up in Angular template

我正在使用 ngRx 文档中的一组非常人为设计的示例来尝试开始为我们的 Angular 应用程序使用 redux 模型。下面的代码有效——所有操作都会触发并正确更新商店。我可以在 redux 开发工具和商店记录器中看到它们。

但是,我无法在模板中显示任何内容。它只是空白。我不确定它是否与看起来像这样的状态树的三层有关:

grandpa: {
  grandpa: {
    grandpaCounter: 50
  }
}

我试图通过使用 reselect 来遵循 ngRx example-app,但也许我误用了那些选择器?我还能缺少什么?

app.component.html

<button (click)="increment()">Increment</button>
<button (click)="decrement()">Decrement</button>
<div>Current Count: {{ grandpaCounter$ | async }}</div>

<button (click)="resetCounter()">Reset Counter</button>

app.module.ts

// Native angular modules
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser' /* Registers critical application service providers */
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { HttpClientModule } from '@angular/common/http'

// Bootstrap component
import { AppComponent } from './app.component'

// ngRx
import { StoreModule } from '@ngrx/store'
import { StoreDevtoolsModule } from '@ngrx/store-devtools'
import { EffectsModule } from '@ngrx/effects'
import {
  StoreRouterConnectingModule,
  routerReducer as router
} from '@ngrx/router-store'

// Router
import { AppRoutingModule } from './app.routing'

// Shared module
import { SharedModule } from './shared/shared.module'

// Functional modules
import { GrandpaModule } from './modules/grandpa/grandpa.module'

// ngRx store middleware
import { metaReducers } from './app.store'

// Configuration
import { APP_CONFIG, AppConfig } from './app.config'

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    StoreModule.forRoot({ router: router }, { metaReducers }),
    StoreRouterConnectingModule,
    StoreDevtoolsModule.instrument({
      maxAge: 25 //  Retains last 25 states
    }),
    EffectsModule.forRoot([]),
    HttpClientModule,
    SharedModule,
    GrandpaModule,
    AppRoutingModule // must be last
  ],
  declarations: [AppComponent],
  providers: [{ provide: APP_CONFIG, useValue: AppConfig }],
  bootstrap: [AppComponent]
})
export class AppModule {}

app.store.ts

// ngRx
import { ActionReducer, MetaReducer } from '@ngrx/store'

import { storeLogger } from 'ngrx-store-logger'

// Root state
export interface State {}

/* Meta-reducers */
export function logger(reducer: ActionReducer<State>): any {
  // default, no options
  return storeLogger()(reducer)
}

export const metaReducers = process.env.ENV === 'production' ? [] : [logger]

grandpa.module.ts

// Native angular modules
import { NgModule } from '@angular/core'

// ngRx
import { StoreModule } from '@ngrx/store'

// Shared module
import { SharedModule } from '../../shared/shared.module'

// Functional Components
import { GrandpaComponent } from './grandpa.component'

// Router
import { GrandpaRoutingModule } from './grandpa.routing'

// Store
import { branchReducers } from './grandpa.store'

@NgModule({
  imports: [
    StoreModule.forFeature('grandpa', branchReducers),
    SharedModule,
    GrandpaRoutingModule // must be last
  ],
  declarations: [GrandpaComponent]
})
export class GrandpaModule {}

grandpa.store.ts

// ngRx
import { ActionReducerMap, createSelector } from '@ngrx/store'

// Module branch reducers
import * as grandpaReducer from './grandpa.reducer'

// Feature state
export interface State {
  grandpa: grandpaReducer.State
}

// Feature reducers map
export const branchReducers: ActionReducerMap<State> = {
  grandpa: grandpaReducer.reducer
}

// Module selectors
export const getGrandpaState = (state: State) => state.grandpa

export const getGrandpaCounter = createSelector(
  getGrandpaState,
  grandpaReducer.getGrandpaCounter
)

grandpa.reducer.ts

import { createSelector } from '@ngrx/store'

import * as GrandpaActions from './grandpa.actions'
import * as grandpaStore from './grandpa.store'

export interface State {
  grandpaCounter: number
}

export const initialState: State = {
  grandpaCounter: 50
}

export function reducer(state = initialState, action: GrandpaActions.Actions) {
  switch (action.type) {
    case GrandpaActions.INCREMENT:
      return { grandpaCounter: state.grandpaCounter + 1 }

    case GrandpaActions.DECREMENT:
      return { grandpaCounter: state.grandpaCounter - 1 }

    case GrandpaActions.RESET_COUNTER:
      return { grandpaCounter: initialState.grandpaCounter }

    default:
      return { grandpaCounter: state.grandpaCounter }
  }
}

// Selectors
export const getGrandpaCounter = (state: State) => state.grandpaCounter

grandpa.component.ts

import { Component } from '@angular/core'

import { Observable } from 'rxjs/Observable'

import { Store } from '@ngrx/store'

import * as GrandpaActions from './grandpa.actions'
import * as grandpaStore from './grandpa.store'

@Component({
  selector: 'portal-grandpa',
  templateUrl: './grandpa.component.html'
})
export class GrandpaComponent {
  grandpaCounter$: Observable<number>

  constructor(private store: Store<grandpaStore.State>) {
    this.grandpaCounter$ = store.select(grandpaStore.getGrandpaCounter)
  }
  increment() {
    this.store.dispatch(new GrandpaActions.Increment())
  }
  decrement() {
    this.store.dispatch(new GrandpaActions.Decrement())
  }
  resetCounter() {
    this.store.dispatch(new GrandpaActions.ResetCounter())
  }
}

它最终成为状态树和选择器。我需要将我的选择器切片定义为三个级别的状态:

grandpa.store.ts

// ngRx
import {
  ActionReducerMap,
  createSelector,
  createFeatureSelector
} from '@ngrx/store'

// Branch reducers
import * as grandpaReducer from './grandpa.reducer'

// State
export interface State {
  grandpa: grandpaReducer.State
}

// Reducers map
export const branchReducers: ActionReducerMap<State> = {
  grandpa: grandpaReducer.reducer
}

// Selectors
export const getS0 = createFeatureSelector<State>('grandpa')
export const getS1 = (state: State) => state.grandpa
export const getS01 = createSelector(getS0, getS1)
export const getS2 = createSelector(getS01, grandpaReducer.getGrandpaCounter)

grandpa.component.ts

import { Component } from '@angular/core'

import { Observable } from 'rxjs/Observable'

import { Store } from '@ngrx/store'

import * as GrandpaActions from './grandpa.actions'
import * as grandpaStore from './grandpa.store'

@Component({
  selector: 'portal-grandpa',
  templateUrl: './grandpa.component.html'
})
export class GrandpaComponent {
  grandpaCounter$: Observable<number>

  constructor(private store: Store<grandpaStore.State>) {
    this.grandpaCounter$ = store.select(grandpaStore.getS2)
  }
  increment() {
    this.store.dispatch(new GrandpaActions.Increment())
  }
  decrement() {
    this.store.dispatch(new GrandpaActions.Decrement())
  }
  resetCounter() {
    this.store.dispatch(new GrandpaActions.ResetCounter())
  }
}

我仍然不确定为什么附加减速器状态及其直接选择器不起作用。我原以为这个单一选择器可以使用它:

grandpa.reducer.ts

export const getGrandpaCounter = (state: State) => state.grandpaCounter

grandpa.component.ts

import { Component } from '@angular/core'

import { Observable } from 'rxjs/Observable'

import { Store } from '@ngrx/store'

import * as GrandpaActions from './grandpa.actions'

import * as grandpaReducer from './grandpa.reducer'

@Component({
  selector: 'portal-grandpa',
  templateUrl: './grandpa.component.html'
})
export class GrandpaComponent {
  grandpaCounter$: Observable<number>

  constructor(private store: Store<grandpaReducer.State>) {
    this.grandpaCounter$ = store.select(grandpaReducer.getGrandpaCounter)
  }
  increment() {
    this.store.dispatch(new GrandpaActions.Increment())
  }
  decrement() {
    this.store.dispatch(new GrandpaActions.Decrement())
  }
  resetCounter() {
    this.store.dispatch(new GrandpaActions.ResetCounter())
  }
}