ngrx entity adapter for a nested property - ngrx

I have the following store structure for users:
usersState = {
isLoading: boolean;
items: {
entities: {},
ids: []
}
}
adapter.getSelectors() only works for the top level keys, whereas I want to apply it inside the items key. How can that be done?
Thanks.

From #ngrx/entity - models.d.ts:
getSelectors<V>(selectState: (state: V) => EntityState<T>): EntitySelectors<T, V>;
You can simply provide a projector to adapter.getSelectors() like this:
adapter.getSelectors((state: YourStateType) => state.items)

Related

Several different entities in one reference field

I need to make a referene with different entities within one reference. Is it possible to do this with MikroORM ?
Example of what I need:
export const userSchema = new EntitySchema<any>({
class: userEntity,
tableName: 'users',
properties: {
uuid: { type: 'uuid', primary: true },
bills: {
reference: '1:m',
entity: () => BillsFromBank || BillsFromAnotherBank,
mappedBy: (a) => a._user,
},
},
});
Is it possible to create some conditions for such a connection 1 reference field with different entities?

How to addOne item at the beginning of collection in ngrx/entity

I started using ngrx/entity package, where I can manage store by adapter. There is addOne method I'd like to use, but it adds item to the end of collection. I wanna add one at the beginning. Could you please help me with that? How to add item at the beginning with EntityAdapter.
How I create entity adapter:
export const adapter: EntityAdapter<AssetTreeNode> = createEntityAdapter({
selectId: (model: AssetTreeNode) => model.Id
});
Reducer looks like that:
export function reducer(state: AssetListState = initialState, action: AssetListAction) {
switch (action.type) {
(...)
case ASSET_LIST_ADD_ITEM:
let assetToAdd: AssetTreeNode = Object.assign({} as AssetTreeNode,
action.payload.asset,
{ Id: action.payload.createdAssetId });
return adapter.addOne(assetToAdd, state); <--- I wanna add here at the end.
(...)
default:
return state;
}
}
There is no proper way provided by #ngrx/entity team. One of the answer mentions to use sort-comparator. But i believe using sort-comparator is not the right way to go. Suppose there is two click actions and in one action we need to append item below and in other action on top. here we will run into the same problem again.
I had run into the same issue and my solution to the problem is to reconstruct the list when we want the item on top of the list.
To add at the top of entity list
const { selectAll } = myAdapter.getSelectors();
...
...
on(MyActions.addItem, (state, { item }) =>{
return myAdapter.setAll([item ,...selectAll(state)], { ...state})
}),
To add at the bottom of entity list
on(MyActions.addItem, (state, { item}) =>{
return myAdapter.addOne(item, state)
}),
The only way to change this behavior would be to use the sortComparer when you create the adapter - docs.
export const adapter: EntityAdapter<User> = createEntityAdapter<User>({
sortComparer: (a: User, b: User) => a.name.localeCompare(b.name),
});
Maybe you could place the item at the begining and replace the list
on(addAsset, (state, { payload }) => {
const currentList = Object.values(state.entities);
const newList = [payload, ...currentList];
return adapter.setAll(newList, state);
});

Can you use combineReducers to compose deep state reducers?

Redux docs suggest using normalizr to design the shape of the state like this:
{
entities: {
cards: {
42: {
id: 42,
text: 'Hello',
category: 2
},
43: {
id: 53,
text: 'There?',
category: 1
},
}
categories: {
1: {
id: 1,
name: 'Questions'
}
2: {
id: 2,
name: 'Greetings'
},
}
},
filter: 'SHOW_ALL',
allCardsList: {
isFetching: false,
items: [ 42, 43 ]
},
}
Naturally, this would split into three composable reducers (filter, allThingsList and entities), but it seems to me that I'd want to write separate reducers for entities.cards and entities.categories.
Is there a way to split management of entities into subreducers that would allow composition like this:
let rootReducer = combineReducers({
entities: {
things,
categories
},
filter,
allCardsList
});
Are there any advantages to keeping the cards and categories in entities, instead of keeping on the root level (which will allow composition using combineReducers)?
{
cards: { ... },
categories: { ... },
filter: 'SHOW_ALL',
allCardsList: { ... }
}
Is there a way to split management of entities into subreducers that would allow composition like this?
Sure! combineReducers() just gives you a reducer so you can use it several times:
let rootReducer = combineReducers({
entities: combineReducers({
things,
categories
}),
filter,
allCardsList
});
The shopping-cart example in Redux repo demonstrates this approach.
Are there any advantages to keeping the cards and categories in entities, instead of keeping on the root level?
It’s up to you but I find the structure you suggest easier to work with. It’s indeed easier for understanding to group all entities under a single key, and it is possible with combineReducers() as I show above.

Redux and Normalizr placing single entity into array

I'm using Redux and Normalizr.
I have a state which looks like:
{
entities: {
users: {
1: {...},
2: {...}
}
},
data: {
users: [1,2]
}
}
One of my API endpoints returns an object that includes a single user and some other data:
data = {
user: {id: 3, ...}
otherstuff: {...}
}
const user = new Schema('users');
normalize(data, {user: user})
This returns
{
entities: {
users: {
3: {id: 3, ...}
}
},
result: {
user: 3
}
}
However my data reducer expects to merge in a users array (of ids):
function data(state = {}, action) {
if (action.response && action.response.result) {
return deepmerge(state, action.response.result)
}
return state
}
Ideally I'd address this issue in the normalisation process rather than changing the reducer. Is there an easy way to either get normalizr to parse {user: {id: 3}} into {result: {users: [3]}}? It already changes the entities key to users.
If not, is there a clean and generic reducer-level solution to having this problem across a variety of entity type names?

Computed property in Ember based on async data

I'm trying to use a computed property based on the values from an async, hasMany model property, but cannot get it to display in my view.
MyApp.Foo = DS.Model.extend({
title: DS.attr('string'),
peeps: DS.hasMany('peep', { async: true });
});
MyApp.Peep = DS.Model.extend({
name: DS.attr('string'),
email: DS.attr('string')
});
MyApp.Foo.FIXTURES = [
{ id: 1, title: 'nice', peeps: [1,2] }
];
MyApp.Peep.FIXTURES = [
{ id: 1, name: 'mypeep', email: 'peep#example.com' },
{ id: 2, name: 'mypeep2', email: 'peep2#example.com' }
];
MyApp.FooController = EmberObjectController.extend({
showPeeps: function() {
// This one works for this test data.
// return [{name: 'baz', email: 'bar'}];
var peepList = this.get('content.peeps.[]').then(function(c) {
// This one does not work, even for this test data.
return {name: 'baz', email: 'bar'}];
});
}.property('content.peeps.[]');
});
In my view, something along the lines of:
{#each peep in controller.showPeeps}}{{peep.name}}{{/each}}
I can see all the data in the "then()" using console.log(), and as it indicates in the code comments, it works if I take the return out of the "then()" - but then the real data is empty because it is returned as async. If I try to make it non-async, I get
Uncaught TypeError: Cannot call method 'resolve' of undefined
I've tried many variants of the computed property code (using #each, using model.peeps - all of which correctly show the data in console.log(), but not in the view. In the view, it is always undefined unless I just return dummy data outside of the then() - which displays correctly)
What am I missing?
Don't treat the hasMany relationship as a promise, treat it as an array. That's the whole point of DS.PromiseArray. If you just want the users, don't even bother with the computed property, just use peeps in your template. But, if you need to convert the data somehow, use map.
showPeeps: function() {
return this.get('peeps').map(function(peep) {
return { name: peep.name, email: peep.email };
});
}.property('peeps.#each')
Also, don't watch the [] property. That only updates when an item is added or removed from the array. Your array contents aren't changing, the contents of the contents are changing. You should watch the #each property instead. You also don't need to add [] to the end of the property name, and you don't need to prefix the property with content..

Resources