michaelkrone / ngrx-normalizr Goto Github PK
View Code? Open in Web Editor NEWManaging normalized state in ngrx applications - transparently
Home Page: https://michaelkrone.github.io/ngrx-normalizr/
License: MIT License
Managing normalized state in ngrx applications - transparently
Home Page: https://michaelkrone.github.io/ngrx-normalizr/
License: MIT License
Thanks for the great library! Looks super useful. I do have one issue though. I set everything up as in the README, but I am not using Effects() to pass in data, because the data is generated on the client side. So I use myStore.dispatch(new AddData<Data>()
to add data.
Looking in ngrx-devconsole I can see that the action is going to the store with the correct payload, but my state does not change. It is always empty ...
I just have this as reducer and state:
export interface TemplateState extends NormalizedState { } export const templateReducer: ActionReducerMap<NormalizedState> = { normalized };
Am I missing anything?
Thanks!
Tom
Hi Michael,
Thanks for building this library, its been very useful so far.
Would it be possible to add support for entities that identifiers of type number?
E.g.
export class SomeEntity {
SomeEntityId: number;
otherEntities: AnotherEntity[]'
}
export class AnotherEntity {
AnotherEntityId: number;
}
export someEntitySchema = new schema.Entity(
'SomeEntity',
{
otherEntities: [anotherEntitySchema]
},
{
idAttribute: 'SomeEntityId'
});
export anotherEntitySchema = new schema.Entity('AnotherEntity', {}, idAttribute: 'AnotherEntityId'});
Mostly this already works fine, except that I can't use the provided entityProjecter
which throws a typescript error:
Argument of type '(entities: {}, id: string) => SomeEntity' is not assignable to parameter of type '(s1: {}, s2: number)
Normalizr itself seems to support ids being numbers and strings, so I modified the interfaces in ngrx-normalizr/normalize.d.ts to allow for numbers and this works (for me at least):
import { MemoizedSelector } from '@ngrx/store';
import { schema } from 'normalizr';
export interface EntityMap {
[key: string]: {
[id: string]: any;
[id: number]: any;
};
}
export interface NormalizedState {
normalized: NormalizedEntityState;
}
export interface NormalizedEntityState {
result: string[];
entities: EntityMap;
}
export declare function normalized(state: NormalizedEntityState, action: any): {
result: any;
entities: any;
};
export declare const getNormalizedEntities: MemoizedSelector<any, EntityMap>;
export declare const getResult: MemoizedSelector<any, any[]>;
export interface SchemaSelectors<T> {
getNormalizedEntities: MemoizedSelector<any, EntityMap>;
getEntities: MemoizedSelector<{}, T[]>;
entityProjector: (entities: {}, id: string | number) => T;
entitiesProjector: (entities: {}) => T[];
}
export declare function createSchemaSelectors<T>(schema: schema.Entity): SchemaSelectors<T>;
I can create a pull request for you if you prefer, but I had trouble getting the karma tests to run... so thought I'd just ask in case this would be a simple change for you.
Thanks again,
Cam
Im getting used to this library now.. Thanks for creating this. But, I see that my observables are not fired when I remove data. Not sure if Im doing anything wrong here. AddData
fires my observables (this.store.select
), but the same doesn't fire when I use RemoveData
or RemoveChildData
.
Here's what Im trying to do:
Say I have the following data:
export class User {
id: number;
name: string;
tasks: Task[];
}
export class Task {
id: number;
name: string;
comments: Comment[];
}
export class Comment{
id: number;
text: string;
}
export const userSchema = new schema.Entity('users');
export const commentSchema = new schema.Entity('comments');
export const taskSchema = new schema.Entity('tasks', { comments: [commentSchema] });
Once I use AddData
, I get below normalized data
users: [
"1": {
id: "1",
name: 'Example Task',
tasks: ["2"]
}
],
tasks: [
"2": {
id: "2",
name: 'Example Task',
comments: ["4"]
}
],
comments: [
"4": {
id: "4",
text: "My Comment",
}
]
Now, if I want to remove a task
from the data and use RemoveChildData({id: "2", taskSchema, userSchema, parentId: "1" })
, I can see that it removes the task and reference from the tasks
property under users
.
If I use RemoveData({id: "2", schema: taskSchema, removeChildren: {comments: 'comments'}})
, I see that the task
and the related comments
are removed.
But in both cases, my this.store.select
is not fired.
Hey, thanks for this library, really convenient way to handle normalization with ngrx.
I've got a question or suggestion:
Say you have the following data:
export class Task {
id: number;
name: string;
comments: Comment[];
}
export class Comment{
id: number;
text: string;
}
export const commentSchema = new schema.Entity('comments');
export const taskSchema = new schema.Entity('tasks', { comments: [commentSchema] });
When I now initially load the Tasks and use the AddData action
, the data gets normalized into something like this (example):
tasks: [
"1": {
id: "1",
name: 'Example Task',
comments: ["4"]
}
],
comments: [
"4": {
id: "4",
text: "My Comment",
taskId: "1"
}
]
This is all fine.
But when I now add a new Comment
and receive the response from the server, for example:
{
id: "8",
text: "Second Comment",
taskId: "1"
}
and then use AddData
again, it'll probably only get added to the normalized comments
array and not added to tasks["1"].comments
.
How it should look in the end:
tasks: [
"1": {
id: "1",
name: 'Example Task',
comments: ["4", // "8" <-- How to add this]
}
],
comments: [
"4": {
id: "4",
text: "My Comment",
taskId: "1"
},
"8": {
id: "8",
text: "Second Comment",
taskId: "1"
},
]
Is there any way to achieve this? Or how is one supposed to achieve this behaviour? Sure, it's possible to query the tasks again, with the updated relations, but wouldn't this be a bit overkill? I think there should be a way to achieve this and I think it would be great if this would be possible with this lib (or maybe it already is?).
Hope you can point me in the right direction! ๐
ngrx-normalizr is not updated to work with @ngrx/store version 5.0.0. Maybe just a dependency bump?
Hi, we are using ngrx-store-freeze to keep our state from mutating accidentally. While active the following line in the REMOVE_DATA reducer throws an error: delete entities_3[key][id];
. I think what happens is __assign({}, state.entities)
assigns all the entity keys and the entity dictionaries are still frozen. One would probably have to use an approach similar to lodashs .omit(entities[key], [id]).
Hi,
I have trouble using the library for the following case. When using entities I have access to the createEntityAdapter upsert and uspertMany.
Scenario:
export interface Post {
id: number;
author?: Author[];
}
export interface Author {
id: number;
}
export const authorSchema = new schema.Entity('author');
export const PostSchema = new schema.Entity('Post', {
author: [authorSchema]
});
I am able to insert child Author to the Post. However I have a case where I am inserting the author if it's doesn't exist inside the Post and if existing I want to update these specific Author(s) with the value (basically an upsertMany or upsert). Today when using AddChildData the value is updated however the normalised reference is inserted 2 times (Eg. In post I got author[132,132]).
The question shouldn't be useful to have an uspertMany an upsert function?
Thank you
I cannot understand how I can create a selector to get the feature entities.
Trying to get them using the schemaSelectors.getEntities selector throws an error:
ERROR TypeError: Cannot read property 'entities' of undefined
at normalize.js:99
at store.js:577
at memoized (store.js:519)
at defaultStateFn (store.js:546)
at store.js:585
at memoized (store.js:519)
at store.js:545
at Array.map ()
at defaultStateFn (store.js:545)
at store.js:585
import {
ActionReducerMap,
createFeatureSelector
} from '@ngrx/store';
import {
NormalizedState,
normalized,
createSchemaSelectors
} from 'ngrx-normalizr';
import { Section } from '../../models/section.model';
import { sectionSchema } from '../../schemas/section.schema';
export interface ReportingState extends NormalizedState {
}
export const reducers: ActionReducerMap<NormalizedState> = {
normalized
};
export const getReportingState = createFeatureSelector<ReportingState>(
'reporting'
);
export const getReportingSchemaSelectors = createSchemaSelectors<Section>(sectionSchema);
export const getSectionEntities = createSelector(
fromFeature.getReportingState,
fromFeature.getReportingSchemaSelectors.getEntities
);
Hi, thanks for this lib !
There is a way to select entites by a list of ids ?
Normalizr's schema objects aren't serializable.
Instead of having the action hang on to the schema and doing the normalization in the reducer, the normalization should be done in the action's constructor (just like a classic action creator) which then puts the normalized entities in the action's payload. That way, the action is completely serializable at the time it's dispatched.
If I want to update a certain value (or values) in an object (whether the top-level, a child, or a grandchild), what is the preferred option? If I pass the ID and the name/value of the property and use the Add/Set methods, then any other properties that I'm not passing are removed.
@ngrx/entity (https://github.com/ngrx/platform/blob/master/docs/entity/adapter.md) has update methods which take a Partial<T> to only update the changes - have you considered something similar?
Hi and great job for this library !
All is fine, except i can't work with ngrx-normalizr "selectors" correctly when i put it in a feature module.
Example :
I have a store made of 2 feature modules (user and templates), so the root store structure is like this :
{
user : IUser,
templates : {normalized, other props...}
}
So, in my template feature module, i define selectors like this :
const templateFeatureSelector = createFeatureSelector<template_state.ITemplates>('templates');
/**
* Get all templates
*/
const templateSchemaSelector = createSchemaSelectors<template_model.ITemplate>(template_model.schemas);
const getAllTemplates = createSelector(templateFeatureSelector,templateSchemaSelector.getNormalizedEntities, templateSchemaSelector.entitiesProjector);
and then in the feature module component, i do this to get all "templates" :
constructor(private store: Store<any>) {
this.templates$ = this.store.select(templates_selectors.templates.getAllTemplates);
this.templates$.subscribe((data) => {
let a = 0;
});
}
It doesn't work since the data value in "subscribe" is always returnin the "full" store object.
After few tests, i get that in my selector the input of "createFeatureSelector" is never take into account, it is always ignored.
So, in normalizr.js, the "state" passed to the "getNormalizedState" selector is alway the root Store (with both "user" and "templates" parts) and never the feature module store (only "templates" part)
At this time, the only workaround i found is to do this when i want to select data in my component :
this.templates$ = this.store.select(templates_selectors.templates.feature).select(templates_selectors.templates.getAllTemplates);
==> I must first select the "feature" store slice, then obtain the correct store, then select against this store with ngrx-normalizr "createSchemaSelector" utility to get my data.
Is this by design or am I wrong ?
Thank you
This more than a issue is a question, but I did not find where to put it
I other implementations using normalizr why saw that they use normalize at the reducer instead of the action creator, why did you decide to call it there ?
When I delete something from my store using the RemoveData-action the item gets deleted from the store, but the reference from the parent does not get removed.
Examples
{ "id": "page1", "sections": [{ "id": "section1" }] }
normalizr turns this into:
{ normalized: { result: [ 'page1' ], entities: { sections: { sektion1: { id: 'sektion1' } }, pages: { seite1: { id: 'seite1', sections: ['sektion1'] } } } }
Now I run this.store.dispatch(new RemoveData({ id: section.id, schema: sectionSchema }));
which leads to the following state:
{ normalized: { result: [ 'page1' ], entities: { sections: { }, pages: { seite1: { id: 'seite1', sections: ['sektion1'] } } } }
Which obviously breaks the code then ... any ideas why this is happening?
Following on from #26 which mentions @ngrx/entity (https://github.com/ngrx/platform/blob/master/docs/entity/adapter.md) and its support for updating partial data, it may also be useful to have the equivalent of its 'removeAll' action so that all the normalized entities can be cleared out without needing to call RemoveData multiple times with all the individual IDs
I suppose different users might have different requirements - for some it could be to empty everything, for others it could be to specify a certain key and only empty that and all its children (although in that case I guess it could get a bit messy if the children were shared with other parent entities that had a different key that weren't being cleared).
Hi,
I've an existing app using your library with the normalized reducer in the root app module. I'm now trying to add normalized state to a feature module, while also keeping the root normalized state.
To enable the normalizing reducer to store normalized data, you have to add it to your state. The best place for this might be the root state of your application, but feature states may use their own normalized state as well.
My feature module's state extends NormalizedState
and I've added the normalized
reducer to my reducer map.
How do I ensure that the schema bound actions are only picked up in the feature state? At the moment both the root and the feature normalized reducers are handling schema bound actions, resulting in duplicated state in the store.
Thanks!
Cam
Say, I have below schema
import { schema } from 'normalizr';
export class Foo {
id: string;
}
// schema
export const fooSchema = new schema.Entity('foo');
And my api call returns a single object.
{
id: '1'
}
when I do a new AddData<Foo>({ data, fooSchema })
in my @effect, I get below error.
Is this a version problem? I am on @ngrx/store 5.0.0
[ts]
Argument of type '{ data: Foo; fooSchema: Entity; }' is not assignable to parameter of type 'NormalizeActionConfig<Foo>'.
Object literal may only specify known properties, and 'fooSchema' does not exist in type 'NormalizeActionConfig<Foo>'.
Hi,
I have a question on best practice of using ngrx-normalizr. If I want to add a child to a deeper nested element, how would you do it?
My element looks like this:
{ "id": "page1", "sections": [{ "id": "section1", "fields": [{ "id": "field1" }] }] }
Now I want to add a new field and of course I have to tell the storage, that I want the new field, within section1, within page1. During updating this is not a problem, since all ids are unique. But when adding I have to tell the store at least the section.
When adding a field using AddAction<Field>
it gets added correctly but not linked to any section. How do you solve this in the best way? Adding the field and then linking it to a section? Or basically by taking the section, adding the field to the section and then "updating" the whole section.
Thanks!
Tom
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.