2016-12-19 2 views
5

У меня есть несколько следующих состояний приложения дерево:ngrx - обновление одного элемента в списке элементов

AppState 
{ 
    MyPage1 - MyList - MyItem[] 
    MyPage2 - MySubPage - MyList - MyItem[] 
    MyPage3 - MyItem //can be stand-alone as well 
} 

Итак, у меня есть список элементов на странице. Я загружаю этот список, отправив действие в редуктор MyList. Как только элементы загружаются, они передаются в качестве входа в MyListComponent, что соответственно создает MyItemComponents с соответствующими входами. MyItemComponent может быть автономным. Таким образом, это не всегда зависит от MyList.

Дело в том, что каждый из MyItems имеет свои собственные действия (например, их можно любить или редактировать). И я в настоящее время застрял где и какие действия я должен отправить в этом случае. Как определить, какой элемент был обновлен и какой список я должен обновить.

Каким будет поток действий при обновлении пользователем одного из элементов в списке?

А также - было ли обнаружено изменение состояния MyList, если я сменил один из MyItem напрямую?

Сценарий я мог думать о том, (в то время как имеющие все необходимые действия на самом MyItem), чтобы добавить дополнительные действия в MyList - например:

updateItem(payload: 
    { 
     updateType, //e.g. like, edit, delete 
     data,  //data needed to make an update 
     itemId  //index in the MyItem array 
    }) 

Потом, как-то я огонь другое действие от MyList до указанного MyItem из массива, элемент запускает соответствующее действие для обновления своего собственного состояния (обновляется через @Effect) Если обновление было успешным, для обновления списка запускается новое действие.

Чтобы быть честным, все это выглядит довольно подробным и неопределенным. Даже не уверен, как это будет реализовано на практике. На самом деле нужно предложение от кого-то с опытом.

p.s. Я должен упомянуть, что все данные извлекаются и отправляются обратно из/в REST api.

Редактировать: Если после подхода от Comprehensive Introduction to @ngrx/store приложение будет иметь тоны повторяющихся действий (каждый контейнер - MyList - повторил бы все действия из MyItem). Будет ли более подходящий подход для моего дела?

+0

Рекомендация: хороший вопрос! –

ответ

11

Способ сделать это, чтобы иметь вспомогательный редуктор, который обрабатывает вспомогательную часть дерева состояний.

В случае, когда вам нужно отправить действия одному элементу массива, вы передаете все эти действия второму редуктору, который обрабатывает один элемент.

Вот пример новости, у которой есть комментарии. Надеюсь, это немного облегчит ситуацию.

import { Action } from '@ngrx/store'; 
import { AppState } from '../reducer'; 
import { NewsItem } from './news.model'; 
import * as newsActions from './news.actions'; 

export interface NewsState { 
    readonly loading: boolean; 
    readonly entities: NewsItem[]; 
} 

export interface AppStateWithNews extends AppState { 
    readonly news: NewsState; 
} 

const initialState: NewsState = { 
    loading: false, 
    entities: [], 
}; 

const newsItemReducer = (newsItem: NewsItem, action: newsActions.NewsActionTypes): NewsItem => { 

    switch (action.type) { 
    case newsActions.Types.UPDATE: 
    case newsActions.Types.REMOVE: 
    case newsActions.Types.COMMENT_CREATE: 
    case newsActions.Types.COMMENT_REMOVE: 
     return Object.assign({}, newsItem, { 
     action: true 
     }); 

    case newsActions.Types.UPDATE_SUCCESS: 
     return Object.assign({}, action.payload, { 
     action: false 
     }); 

    case newsActions.Types.COMMENT_CREATE_SUCCESS: 
     return Object.assign({}, newsItem, { 
     action: false, 
     comments: [action.payload.comment, ...newsItem.comments] 
     }); 

    case newsActions.Types.COMMENT_REMOVE_SUCCESS: 
     return Object.assign({}, newsItem, { 
     action: false, 
     comments: newsItem.comments.filter(i => i.id !== action.payload.commentId) 
     }); 

    default: 
     return newsItem; 
    } 
}; 

export const newsReducer = (state: NewsState = initialState, action: newsActions.NewsActionTypes): NewsState => { 

    switch (action.type) { 
    case newsActions.Types.LIST: 
     return Object.assign({}, state, { loading: true }); 

    case newsActions.Types.LIST_SUCCESS: 
     return { 
     loading: false, 
     entities: action.payload 
     }; 

    case newsActions.Types.CREATE_SUCCESS: 
     return Object.assign({ 
     loading: false, 
     entities: [action.payload, ...state.entities] 
     }); 

    case newsActions.Types.REMOVE_SUCCESS: 
     return Object.assign({ 
     loading: false, 
     entities: state.entities.filter(newsItem => newsItem !== action.payload) 
     }); 

    // Delegate to newsItemReducer 
    case newsActions.Types.UPDATE: 
    case newsActions.Types.UPDATE_SUCCESS: 
    case newsActions.Types.COMMENT_CREATE: 
    case newsActions.Types.COMMENT_CREATE_SUCCESS: 
    case newsActions.Types.COMMENT_REMOVE: 
    case newsActions.Types.COMMENT_REMOVE_SUCCESS: 
     return Object.assign({}, state, { 
     entities: state.entities.map(newsItem => { 
      // Only call sub reducer if the incoming actions id matches 
      if (newsItem.id === (action.payload.newsItem || action.payload).id) { 
      return newsItemReducer(newsItem, action); 
      } 
      return newsItem; 
     }) 
     }); 

    default: 
     return state; 
    } 

}; 

И посмотреть, как назвать их здесь новостями.

import { Action } from '@ngrx/Store'; 
import { NewsItem } from './news.model'; 
import { Response } from '@angular/http'; 

export const Types = { 
    LIST: '[News] List', 
    LIST_SUCCESS: '[News] List Success', 
    LIST_ERROR: '[News] List ERROR', 

    CREATE: '[News] Create', 
    CREATE_SUCCESS: '[News] Create Success', 
    CREATE_ERROR: '[News] Create Error', 

    UPDATE: '[News] Update', 
    UPDATE_SUCCESS: '[News] Update Success', 
    UPDATE_ERROR: '[News] Update Error', 

    REMOVE: '[News] Remove', 
    REMOVE_SUCCESS: '[News] Remove Success', 
    REMOVE_ERROR: '[News] Remove Error', 

    COMMENT_CREATE: '[News] Comment Create', 
    COMMENT_CREATE_SUCCESS: '[News] Comment Create Success', 
    COMMENT_CREATE_ERROR: '[News] Comment Create Error', 

    COMMENT_REMOVE: '[News] Comment Remove', 
    COMMENT_REMOVE_SUCCESS: '[News] Comment Remove Success', 
    COMMENT_REMOVE_ERROR: '[News] Comment Remove Error', 
}; 

/** 
* List 
*/ 
export class List implements Action { 
    type = Types.LIST; 
    constructor(public payload?: any) {} 
} 
export class ListSuccess implements Action { 
    type = Types.LIST_SUCCESS; 
    constructor(public payload: NewsItem[]) {} 
} 
export class ListError implements Action { 
    type = Types.LIST_ERROR; 
    constructor(public payload: Response) {} 
} 

/** 
* Create 
*/ 
export class Create implements Action { 
    type = Types.CREATE; 
    constructor(public payload: NewsItem) {} 
} 
export class CreateSuccess implements Action { 
    type = Types.CREATE_SUCCESS; 
    constructor(public payload: NewsItem) {} 
} 
export class CreateError implements Action { 
    type = Types.CREATE_ERROR; 
    constructor(public payload: Response) {} 
} 

/** 
* Update 
*/ 
export class Update implements Action { 
    type = Types.UPDATE; 
    constructor(public payload: NewsItem) {} 
} 
export class UpdateSuccess implements Action { 
    type = Types.UPDATE_SUCCESS; 
    constructor(public payload: NewsItem) {} 
} 
export class UpdateError implements Action { 
    type = Types.UPDATE_ERROR; 
    constructor(public payload: Response) {} 
} 

/** 
* Remove 
*/ 
export class Remove implements Action { 
    type = Types.REMOVE; 
    constructor(public payload: NewsItem) {} 
} 
export class RemoveSuccess implements Action { 
    type = Types.REMOVE_SUCCESS; 
    constructor(public payload: NewsItem) {} 
} 
export class RemoveError implements Action { 
    type = Types.REMOVE_ERROR; 
    constructor(public payload: Response) {} 
} 

/** 
* Create Comment 
*/ 
export class CommentCreate implements Action { 
    type = Types.COMMENT_CREATE; 
    payload: { 
    newsItem: NewsItem, 
    comment: string 
    }; 

    constructor(newsItem: NewsItem, comment: string) { 
    this.payload = { 
     newsItem, 
     comment 
    }; 
    } 
} 
export class CommentCreateSuccess implements Action { 
    type = Types.COMMENT_CREATE_SUCCESS; 
    payload: { 
    newsItem: NewsItem, 
    comment: string 
    }; 
    constructor(newsItem: NewsItem, comment: string) { 
    this.payload = { 
     newsItem, 
     comment 
    }; 
    } 
} 
export class CommentCreateError implements Action { 
    type = Types.COMMENT_CREATE_ERROR; 
    constructor(public payload: Response) {} 
} 

/** 
* Remove Comment 
*/ 
export class CommentRemove implements Action { 
    type = Types.COMMENT_REMOVE; 
    payload: { 
    newsItem: NewsItem, 
    commentId: string 
    }; 

    constructor(newsItem: NewsItem, commentId: string) { 
    this.payload = { 
     newsItem, 
     commentId 
    }; 
    } 
} 
export class CommentRemoveSuccess implements Action { 
    type = Types.COMMENT_REMOVE_SUCCESS; 
    payload: { 
    newsItem: NewsItem, 
    commentId: string 
    }; 
    constructor(newsItem: NewsItem, commentId: string) { 
    this.payload = { 
     newsItem, 
     commentId 
    }; 
    } 
} 
export class CommentRemoveError implements Action { 
    type = Types.COMMENT_REMOVE_ERROR; 
    constructor(public payload: Response) {} 
} 

export type NewsActionTypes = 
    List 
    | ListSuccess 
    | ListError 
    | Create 
    | CreateSuccess 
    | CreateError 
    | Update 
    | UpdateSuccess 
    | UpdateError 
    | Remove 
    | RemoveSuccess 
    | RemoveError 
    | CommentCreate 
    | CommentCreateSuccess 
    | CommentCreateError 
    | CommentRemove 
    | CommentRemoveSuccess 
    | CommentRemoveError; 
+1

Спасибо! Ваш ответ очень помог, и мне нравится, насколько чистым является ваш код –

+0

- это разница между выполнением всего этого только в новостной редакции, просто разделение проблем + модульный код, или есть реальная необходимость, чтобы «ngrx/store» знали об отдельных применяемые редукторы? И.Е. ngrx будет вести себя по-другому, если бы вы объединили все «newsItemReducer» в 'newsReducer' и просто сделали« состояние ».map' и вернуть обновленное 'newsState'? –

+0

Это просто для разделения проблем. И легче рассуждать об одном элементе, чем в коллекции. –

Смежные вопросы