使用 angularfire2(v5) 和 ngrx 效果从 Firebase 获取列表作为数组

Get list as array from Firebase with angularfire2(v5) and ngrx effects

我必须告诉你,我快被它逼疯了。我正在尝试使用 AngularFire2(v.5) 从 Firebase 获取数据,然后在@ngrx/effects 上使用它并将其存储在@ngrx/store 上。好吧,因为我需要带键的数据,所以我的效果代码如下所示:

spaces.effects.ts

@Effect()
  getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
  .switchMap((action: SpacesActions.GetSpacesRequest) => {
    return this.afs.list<Space>('/spaces').snapshotChanges()
      .switchMap(actions => {
      console.log('action is ', actions);
      return actions.map(space => {
      const $key = space.payload.key;
      const data: Space = { $key, ...space.payload.val() };
      console.log('snapshot is: ', data);
      return new SpacesActions.GetSpacesSuccess(data);
      });
    }
  );

我的 "actions" 带有数据和密钥,然后我得到每个项目的密钥,因为这样我就可以轻松地更新和删除项目。我的数据库有 3 个项目和 3 个键。如果我 运行 这段代码并将其记录下来,首先我可以看到 1 个数组中的所有项目及其有效负载,然后在第二个日志中我将每个有效负载视为快照。

当我调用 GetSpacesSuccess 时,我想发送我得到的所有快照(带有密钥和项目)然后存储它。我现在的做法是分派此操作 3 次,我只能在屏幕上看到 2 个项目,因为第一个被第二个覆盖。

所以,有两个问题:有没有更简单的方法可以使用密钥从 firebase 获取项目,然后使用 @ngrx 存储它们?如果不是,我做错了什么,我的第一个项目被覆盖并且我的操作被派遣了 3 次?

拜托,我正在尽最大努力学习。谢谢!

spaces.reducers.ts

    case SpacesActions.GET_SPACES_REQUEST:
    return {
      state,
      spaces: null,
      loading: true
  };
    case SpacesActions.GET_SPACES_SUCCESS:
    return {
      ...state,
      ...action.payload,
      spaces: [state, action.payload],
      loading: false
    };

spaces.actions.ts

export class GetSpacesRequest implements Action {
  readonly type = GET_SPACES_REQUEST;

}

export class GetSpacesSuccess implements Action {
  readonly type = GET_SPACES_SUCCESS;

  constructor(public payload: Space) {} <<<<<HERE I'D LIKE TO GET THE FULL ARRAY WITH EACH KEY
}

如果我对你的问题的理解正确,你想为每个项目存储也存储它的密钥。您正在寻找 Map.

我会按如下方式构建您的功能。

spaces.actions.ts:

加载space不需要payload,而成功只有Space的数组。我认为您应该在减速器中构建 Map<string,Space>string 是您的密钥)。

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

/** App Models **/
import { Space } from './space.model';

export const GET_SPACES = '[Spaces] Spaces get';
export const GET_SPACES_SUCCESS = '[Start] Spaces get - Success';

export class GetSpacesAction implements Action {
    readonly type = GET_SPACES;
}

export class GetSpacesActionSuccess implements Action {
    readonly type = GET_SPACES_SUCCESS;
    constructor(public payload: Space[]) {}
}

export type All
    = GetSpacesAction
    | GetSpacesActionSuccess;

spaces.effects.ts:

我假设您只需要一种方法来获取 spaces。如果您需要做其他事情,只需编辑这段代码即可。 spaceService.getSpaces() 应该 return 只有 Space 的数组。因此,创建一个新的 Space 模型,并在您的服务上将每个 json 条目映射到 new Space().

import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';

/** rxjs **/
import {map} from 'rxjs/operators/map';
import {mergeMap} from 'rxjs/operators/mergeMap';
import {catchError} from 'rxjs/operators/catchError';

/** ngrx **/
import * as spacesActions from './spaces.actions';

/** App Services **/
import { SpacesService } from './spaces.service';

@Injectable()
export class SpacesEffects {

    @Effect() getSpaces$ = this.actions$
      .ofType(spaceActions.GET_SPACES)
        .pipe(
          mergeMap(() => {
            return this.spaceService.getSpaces()
              .pipe(
                map((spaces) => {
                  return new spacesActions.GetSpacesActionSuccess(spaces);
                }),
                catchError((error: Error) => {
                  // Handle erro here
                })
              );
          })
        )
    ;

    constructor(private spacesService: SpacesService, private actions$: Actions) { }

}

spaces.reducer.ts

在这里你可以构建你的地图,你也可以为 return 创建一个新的动作,例如,给定它的键 space。我认为您在这里不需要任何加载参数,我猜您是在视图中使用它进行某些加载处理,只需在视图中使用 AsyncPipe 并使用 *ngIf 检查是否有加载动画spaces与否。

/** ngrx **/
import {createFeatureSelector} from '@ngrx/store';
import {createSelector} from '@ngrx/store';
import * as spacesActions from './spaces.actions';

export type Action = spacesActions.All;

/** App Models **/
import { Space } from './space.model';

export interface SpaceState {
    keySpaces: Map<string, Space>;
    spaces: Space[];
    keys: string[];
}

export const initialState: SpaceState = {
    keySpaces: new Map<string, Space>(),
    spaces: [],
    keys: []
};

// Selectors
export const selectSpace  = createFeatureSelector<SpaceState>('space');
export const getKeySpaces = createSelector(selectSpace, (state: StartState) => {
  return state.keySpaces;
});
export const getSpaces = createSelector(selectSpace, (state: SpaceState) => {
  return state.spaces;
});
export const getKeys = createSelector(selectSpace, (state: SpaceState) => {
  return state.keys;
});

export function spacesReducer(state: SpaceState = initialState, action: Action): SpaceState {
    switch (action.type) {
        case startActions.GET_SPACES_SUCCESS:
            // Since we return this from effect
            const fetchedSpaces = action.payload;  
            const fetchedKeys = [];
            const keySpacesMap = new Map<string, Space>();
            fetchedSpaces.forEach( (space: Space) => {
                fetchedkeys = fetchedKeys.concat(space.key);
                keySpacesMap.set(space.key, new Space(space));
            }
            returns {
                ...state,
                keySpaces: keySpacesMap,
                spaces: fetchedSpaces,
                keys: fetchedkeys
            }
        default: {
            return state;
        }
    }
}

最后,您应该能够在组件中获取此类参数,例如:

. . .

 keySpaces$ = Observable<Map<string, Space>>;
 spaces$ = Observable<Array<Space>>;
 keys$ = Observable<Array<string>>;

 constructor(private _store: Store<AppState>) {
     this.keySpaces$ = this._store.select(getKeySpaces);
     this.space$s = this._store.select(getSpaces);
     this.keys$ = this._store.select(getKeys);
 }

 . . .

 ngOnInit() {
     this._store.dispatch(new spacesActions.GetSpacesAction);
 }

. . .

当然是把新状态加到AppState:

. . .

export interface AppState {
  . . .
  space: SpaceState;
}

感谢@AndreaM16 提供最完整的答案。我整夜都在研究它,结果我做了不同的事情。实际上,在学习过程中,我们犯错是为了获得知识。可能你的解决方案比我的好,我会研究,谢谢。如果可能的话,我很想听听您对我的解决方案的评论。

最后,在阅读了大量文档之后,我的效果现在是这个,但是我没有任何错误捕捉器:

private spacesList = 'spaces/';
@Effect()
  getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
  .switchMap(payload => this.afs.list(this.spacesList).snapshotChanges()
    .map(spaces => {
      return spaces.map(
          res => {
            const $key = res.payload.key;
            const space: Space = {$key, ...res.payload.val()};
            return space;
          }
    );
    })
    .map(res =>
      new SpacesActions.GetSpacesSuccess(res)
    ));

减速器

    case SpacesActions.GET_SPACES_REQUEST:
    return Object.assign({}, state, {
      spaces: null,
      loading: true
  });
    case SpacesActions.GET_SPACES_SUCCESS:
    return Object.assign({}, state, {
      spaces: action.payload,
      loading: false
  });

操作数

export class GetSpacesRequest implements Action {
  readonly type = GET_SPACES_REQUEST;

}

export class GetSpacesSuccess implements Action {
  readonly type = GET_SPACES_SUCCESS;

  constructor(public payload: Space[]) {}
}

并且,在我的组件中,我需要列表的地方:

 constructor(private store: Store<fromSpaces.FeatureState>) {}
ngOnInit() {
    this.store.dispatch(new SpacesActions.GetSpacesRequest());
    this.spacesState = this.store.select('spaces');
}