Let me try to explain a method for setting up an array of objects in a way that is easy to understand for everyone.
In the ngrx example app, they demonstrate how to store an array of objects and retrieve a specific row by its id or key. To achieve this, you can create an object that contains your object, with the object's id as the property key. This allows you to add properties to the "entities" Object using any string as the property name. For instance, if your state has a property called "entities", whose value is an empty object, you can then populate this object by creating properties based on your array elements.
export interface BooksState {
entities: { [id: string]: Book };
};
Let's start by adding one row from the book objects array to the "entities" object like this.
reducerState.entities[book.id] = book;
This code snippet will set the book's id as a property on the "entities" object. If you were to inspect it in the console or debug tools, it might look similar to this after creation:
reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }
The ngrx example app works with the entire array by adding it to the state using the reduce operator in JavaScript.
const newBookEntities
= newBooks.reduce(
(entities: { [id: string]: Book }, book: Book) =>
{
return Object.assign(entities, { [book.id]: book });
}, {});
Special functions are created to access parts of this state, which can later be combined to obtain specific rows.
export function getBooksState() {
return (state$: Observable<AppState>) => state$
.select(s => s.books);
}
export function getBookEntities() {
return (state$: Observable<BooksState>) => state$
.select(s => s.entities);
};
export function getBooks(bookIds: string[]) {
return (state$: Observable<BooksState>) => state$
.let(getBookEntities())
.map(entities => bookIds.map(id => entities[id]));
}
These functions are consolidated in the index.ts barrel in the example:
import { compose } from '@ngrx/core/compose';
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
This function chains calls together from right to left, first accessing the reducer state with getBooksState and then calling fromBooks.getBooks with the desired book ids and previous state obtained through getBooksState, allowing mapping to select the required states.
In your component, you can use this function to fetch specific books:
myBooks$: Observable<Books> = this.store.let(getBooks(bookIds)));
The returned observable updates automatically whenever the store changes.
Additional: The "let" operator on the store object passes the current observable holding the store's state object into the function returned by the "getBooks" function.
For more insight, let's take a closer look at the composition of these functions.
export function getBooks(bookIds: string[]) {
return compose(fromBooks.getBooks(bookIds), getBooksState());
}
The compose function combines two functions here - fromBooks.getBooks(bookIds) and getBooksState(). It enables passing a value to the rightmost function and propagating its result down the line. Consequently, we pass the result of getBooksState to fromBooks.getBooks and ultimately return that outcome.
Both functions receive an Observable. getBooksState takes the store state observable object as a parameter, maps/selects it to the relevant slice of the store, and returns the new mapped observable. This transformed observable is then passed to fromBooks.getBooks, which further filters out only the necessary entities. Finally, the resultant observable is what gets returned via the "store.let" call.
If you need further clarifications, feel free to ask questions so I can assist you better.
https://github.com/ngrx/example-app