使用 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');
}
我必须告诉你,我快被它逼疯了。我正在尝试使用 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');
}