web-dev-qa-db-fra.com

ngrx - met à jour un seul élément dans une liste d'éléments

J'ai un peu l'arborescence de l'état d'application suivante:

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

Donc, j'ai une liste d'articles sur une page. Je charge cette liste en envoyant une action au réducteur MyList. Une fois les éléments chargés, ils sont passés en entrée au MyListComponent, qui crée en conséquence MyItemComponents avec les entrées correspondantes. MyItemComponent peut également être autonome. Donc, cela ne dépend pas toujours de MyList.

Le fait est que chacun des MyItems a ses propres actions (par exemple, peut être aimé ou modifié). Et je suis actuellement coincé où et quelle action dois-je envoyer dans ce cas. Comment identifier quel élément a été mis à jour et quelle liste dois-je mettre à jour.

Quel serait le flux d'actions lorsque l'utilisateur met à jour l'un des éléments d'une liste?

Et aussi - le changement d'état de MyList serait-il détecté si je change directement l'un des MyItem?

Le scénario auquel je pourrais penser est (tout en ayant toutes les actions appropriées sur MyItem lui-même) d'ajouter des actions supplémentaires à la MyList - par exemple:

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

Ensuite, d'une manière ou d'une autre, je déclenche une autre action de MyList vers le MyItem spécifié à partir du tableau, l'élément déclenche l'action appropriée pour mettre à jour son propre état (mis à jour via @Effect) Si une mise à jour a réussi, une nouvelle action est déclenchée pour mettre à jour la liste.

Pour être honnête, tout cela semble assez bavard et vague. Je ne sais même pas comment cela serait mis en œuvre dans la pratique. J'ai vraiment besoin des suggestions de quelqu'un qui a de l'expérience.

p.s. Je dois mentionner que toutes les données sont récupérées et renvoyées depuis/vers l'api REST.

Edit: Si vous suivez l'approche de Introduction complète à @ ngrx/store , l'application aurait des tonnes d'actions en double (chaque conteneur - MyList - aurait toutes les actions de MyItem répétées). Y aurait-il une approche plus appropriée pour mon cas?

12

La façon de le faire est d'avoir un sous-réducteur qui gère une sous-partie de l'arbre d'état.

Dans le cas où vous devez envoyer des actions à un seul élément du tableau, vous transmettez toutes ces actions au sous-réducteur qui gère un seul élément.

Voici un exemple d'une nouvelle qui contient des commentaires. J'espère que ça clarifie un peu les choses.

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;
  }

};

Et pour voir comment les appeler, voici les news.actions.

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;
16
Leon Radley