如果找不到所选项,则加载/获取初始列表

时间:2017-11-13 09:10:06

标签: angular rxjs ngrx ngrx-effects

我对ngrx / rxjs世界有点新意,并试图找出如何使用ngrx以正确的方式在我的Angular应用程序中设置我的效果。

我有两种工作方案:

  • 加载/获取列表 - 从api加载列表并存储到状态
  • 选择项目 - 从先前加载的列表中按ID获取项目

现在,我正在寻找的效果逻辑是:

  

如果在状态参数entities: Item[]中找不到所选项 - 并且状态参数initialLoaded===false - 则在尝试按id(再次)选择之前运行操作LIST_LOAD。

或者简单地说:

  

如果initialLoaded===false,则在执行常规ITEM_SELECT之前运行LIST_LOAD。

我目前的ITEM_SELECT效果代码,让我们开始:

@Effect() selectItemEffect: Observable<Action> = this.actions$
        .ofType(MyActions.ActionTypes.ITEM_SELECT)
        .withLatestFrom(this.store$)
        .map(([action, state]) => {

            const id = (<MyActions.ItemSelectAction>action).id;
            const selectedItem = state.myItems.entities.find(x => x.id === id);

            if(selectedItem){
                return new MyActions.SelectSuccessAction(selectedItem);

            } else {
                /* 
                if(!state.myItems.initialLoaded) {
                     // run MyActions.ListLoadAction()?
                     // should be a nicer rxjs method for these scenarios?
                }
                */
                return new MyActions.ItemSelectFailureAction(new Error('Item not found'));

            }
        })

1 个答案:

答案 0 :(得分:0)

设置更简单的解决方案。设置3个不同的操作,2加载项目列表,另一个返回选定的项目。

如果找不到该项,只需触发另一个动作,该动作调用执行api调用的服务以通过其id获取该项。

但是,无论如何,我觉得你是以一种棘手的方式处理它,你可以用这种方式分离动作处理:

  • 效果:仅LOAD_ITEMS
  • Reducer:LOAD_SUCCESSSELECT

让我们假设您有一个Item课程来为您的某个项目建模并且item.idstring,您应该采取以下行动:

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

/** App Models **/
import { Item } from '../models/item.model';

export const LOAD = '[Item] Load';
export const LOAD_SUCCESS = '[Item] Load Success';
export const SELECT = '[Item] Select';

export class LoadAction implements Action {
  readonly type = LOAD;
  constructor() { }
}

export class LoadActionSuccess implements Action {
  readonly type = LOAD_SUCCESS;
  constructor(public payload: Item[]) { }
}

export class SelectAction implements Action {
  readonly type = SELECT;
  constructor(public payload: string) {}
}

export type All
  = LoadAction
  | LoadActionSuccess
  | SelectAction;

然后,您需要一个效果来使用服务执行api调用以返回第一个项目列表:

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 itemsActions from '../actions/items.actions';
/** App Services **/
import { ItemService } from '../services/item.service';
/** App Model **/
import {Item} from '../models/content.model';

@Injectable()
export class ItemsEffects {

  @Effect() load$ = this.actions$
    .ofType(itemsActions.LOAD)
      .pipe(
        mergeMap(() => {
          return this.itemsService.getItemsFromApi()
            .pipe(
              map((items: Item[]) => {
                return new itemsActions.LoadActionSuccess(items);
              }),
              catchError((error: Error) => {
                // handle error here
              })
            );
        })
    )
  ;

  constructor(private itemsService: ItemsService, private actions$: Actions) { }
}

然后你需要一个减速器来处理SuccessSelect

/** ngrx **/
import {createFeatureSelector} from '@ngrx/store';
import {createSelector} from '@ngrx/store';

/** App Models **/
import { Item } from '../models/item.model';

/** ngrx **/
import * as itemsActions from '../actions/items.actions';

export type Action = itemsActions.All;

export interface ItemsState {
  items: Item[];
  selectedItem: Item;
}

export const initialState: ContentsState = {
  items: [],
  selectedItem: new Item()
};

export const selectItems = createFeatureSelector<ItemsState>('items');
export const getItems    = createSelector(selectItems, (state: ItemsState) => {
  return state.items;
});
export const getItemById = createSelector(selectItems, (state: ItemsState) => {
  return state.selectedItem;
});

export function itemsReducer(state: ItemsState = initialState, action: Action): ItemsState {
  switch (action.type) {
    case itemsActions.LOAD_SUCCESS:
      const loadedItems = action.payload.map(item => new Item(item));
      return {
               ...state,
               items: loadedItems,
               selectedItem: state.selectedItem
      };
    case itemsActions.SELECT:
      // Find one item containing id equals to the one we passed
      const filteredItem = state.items.filter((item) => {
          return item.id === action.payload
      })[0];
      return {
               ...state,
               items: state.items,
               selectedItem: filteredItem
      };
    default: {
      return state;
    }
  }
}

当然,在您的组件上,您应该使用以下内容获取新值:

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';

/** rxjs **/
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';

/** ngrx **/
import {AppState} from '../../shared/app-state.interface';
import * as itemsActions from './actions/items.actions';
import {getItemById, getItems} from './reducers/items.reducer';

/** App Models **/
import { Item } from './models/item.model';

@Component({
  selector: 'app-items',
  templateUrl: './items.component.html'
})

export class ItemsComponent implements OnInit {

    items$: Observable<Array<Item>>;
    selectedItems$: Observable<Item>;
    selectItemById$ = new Subject<string>();

    constructor(private store: Store<AppState>) {
        this.items$ = this.store.select(getItems);
        this.selectedItems$ = this.store.select(getItemById);
        this.selectItemById$
            .switchMap(id => this.store.dispatch(new itemsActions.SelectAction(id)));
    }

    ngOnInit() {
         this.store.dispatch(new itemsActions.LoadAction());
    }

}

选择在您的模板中完成,例如:

<... (input)="selectItemById$.next(id)"/>

AppState只是一个界面:

import {ItemsState} from './items/reducers/items.reducers';

export interface AppState {
  items: ItemsState;
}

或以任何其他方式表达。