NgRX - Store Modelling - ngrx

Im creating an interface to send notifications.
The form will have the following fields:
title
message
users <-- Autocomplete
Im not sure how to model the state for this behaviour.
I already have an store:
export interface NotificationState extends EntityState<NotificationModle> {
loading: boolean;
loaded: boolean;
total: number;
current_page: number;
per_page: number;
}
Should I add a new users properties to my state or create a new one?

What you have for NotificationModel is correct.
User is a separate entity and should have its own EntityState.

Related

Redux Toolkit Streaming updates - how to deal with relational entity adapters?

I am working on the chat that is utilising RTK Query with entity adapters.
I currently have 2 different entity adapters; one for chat and one for messages.
How can I go about selecting all messages for a specific chat? Do I need to store array of message ids inside of the chat adapter or do I select all messages and filter them by parent_chat_uuid property?
What is more efficient way to approach this? Is there a built in selector that allows you to select entities by its property rather then the id? I imagine mapping over extremely large array to find object with certain property is pretty expensive.
Thanks
edit:
I am initially querying latest chats and once cacheDataLoaded I add many chats to entity adapter.
I also setup websocket connection and subscribe to 'message' event after data is loaded. Everytime message is notified by ws I addOne message. If status like 'delivered' or 'read' is notified I would call updateOne.
I am actually trying to rewrite project that was not-using RTK Query and entity adapters and messages were always added to the messages array inside of the chat object. Websocket connection and updates were handle by middleware that was calling async thunks -> updating store.
I am trying move all the middleware to be handle inside of the rtk queries / mutation logic and call actions from there directly (or thunks with more logic that call actions).
**off topic and I can create a separate post for this but is there any benefit in creating a App Thunk (not async) that just calls an action? Should I just call actions instead in this case?
I'm an RTK maintainer, and "author" of createEntityAdapter (technically I ported it from NgRX, but I did a lot of work on it).
I actually did something like this myself on a previous project. I'd put a simplified version of the code into a gist, but I'll post it here for posterity too.
The technique that I used was nested entity adapters: a top-level one that stores chat rooms, and a nested one inside each room entry that stores that room's messages:
// Example of using multiple / nested `createEntityAdapter` calls within a single Redux Toolkit slice
interface Message {
id: string;
roomId: string;
text: string;
timestamp: string;
username: string;
}
interface ChatRoomEntry {
id: string;
messages: EntityState<Message>;
}
const roomsAdapter = createEntityAdapter<ChatRoomEntry>();
const messagesAdapter = createEntityAdapter<Message>();
const fetchRooms = createAsyncThunk(
"chats/fetchRooms",
chatsAPI.fetchRooms
);
const fetchMessages = createAsyncThunk(
"chats/fetchMessages",
async (roomId) => {
return chatsAPI.fetchMessages(roomId);
}
)
const chatSlice = createSlice({
name: "chats",
initialState: roomsAdapter.getInitialState(),
reducers: {
},
extraReducers: builder => {
builder.addCase(fetchRooms.fulfilled, (state, action) => {
const roomEntries = action.payload.map(room => {
return {id: room.id, messages: messagesAdapter.getInitialState()};
});
roomsAdapter.setAll(state, roomEntries);
})
.addCase(fetchMessages.fulfilled, (state, action) => {
const roomId = action.meta.arg;
const roomEntry = state.entities[roomId];
if (roomEntry) {
messagesAdapter.setAll(roomEntry.messages, action.payload);
}
})
}
})
/*
Resulting state:
{
ids: ["chatRoom1"],
entities: {
chatRoom1: {
id: "chatRoom1",
messages: {
ids: ["message1", "message2"],
entities: {
message1: {id: "message1", text: "hello"},
message2: {id: "message2", text: "yo"},
}
}
}
}
}
*/
That said, your situation sounds somewhat different, because you specifically said you're using RTK Query and trying to stream something. Because of that, I'm actually not sure how you're trying to set up the endpoints and store the data atm. If you can edit your post with more details and then leave a comment, I can try to update this response to give more advice.
Per the last question specifically: no, there's no built-in selectors for searching by properties, and doing that does generally turn into "re-filter all the items any time there's an update".

How to dispatch an action in NgRx Store DevTools with NgRx 8?

I've updated my ngrx code to version 8 (with Action Creators etc.), but I don't know now how to use Dispatcher within NgRx Store DevTools.
Before I was able to dispatch actions like this:
{
type: '[Something] something loaded',
payload: {
results: ['a', 'b', 'c']
}
}
In my simple app I have the following Action:
export const SaveUserInfo = createAction(
'[User] Save user info',
props<{ user: IUser}>()
);
and the IUser model
export interface IUser {
name: string;
money: number;
}
Than in DevTools I dispatch like this:
{
user: {
name: 'coiso',
money: 1000
},
type: '[User] Save user info'
}
Hope it works for you.

AppSync #connection not making two-way connection

I'm using graphql through AWS AppSync. Given my models below I would expect when I successfully createClassroom with createClassroomInput that the teacherClassrooms would have a new Classroom associated to it and that the newly created Classroom would have a teacher associated to it.
The outcome, however, is that Classroom is created and the User is correctly associated with the new Classroom but the Classroom is not associated to the existing User.
type User #model {
id: ID!
userType: String!
teacherClassrooms: [Classroom] #connection(name: "TeacherClassrooms")
}
type Classroom #model {
id: ID!
teacher: User #connection(name: "TeacherClassrooms")
linkCode: String!
name: String!
}
export type CreateClassroomInput = {
id?: string | null,
linkCode: string,
name: string,
classroomTeacherId?: string | null,
};
So, if I query for listClassrooms, each classroom comes back with its associated User. But if I query for a User they do not have any classrooms in their teacherClassrooms array.
Do I need to updateUser when I create a new Classroom? My intuition, and my understanding of the docs, lead me to believe that AppSync would handle both updates when #connection is specified on a model property.
Or is this just a way of indicating to the backend that "for each Id in this property array assume it's of type X and when queried go fetch by Id against the corresponding table"?
Check your list query. I had the same issue and then realised that generated list query was missing relation attributes after I updated schema with name connection. In your case something like this
listUsers {
items {
id
teacherClassrooms {
items {
linkCode
id name
}
}
}
}
inside your listUsers query

Addtional parameter with #ngrx/entity

I want to keep employee array and page loading status in store state. So my initial state will look like this
const initialState = {
isLoading: false,
employees: []
};
Now i want to use #ngrx/entity for employee instead of array. The documentation only show the demo for using entity with entire state.
How can i use entity for only one property rather than entire state?
If it's not possible what is the alternative for above scenario?
See the docs for an example:
import { EntityState, EntityAdapter, createEntityAdapter } from '#ngrx/entity';
export interface User {
id: string;
name: string;
}
export interface State extends EntityState<User> {
// additional entities state properties
selectedUserId: number;
}
export const adapter: EntityAdapter<User> = createEntityAdapter<User>();

Meteor - Validating new user document serverside

I'm having trouble with this seemingly trivial stuff, harrrr!
I have this user document:
userData = {
account: {
type: 'free'
},
profile: {
name: 'Artem',
},
username: 'aaa#gmail.com',
password: '123'
};
Which I'm sending client-side: Accounts.createUser(userData);
Then server side I want to check if account type equals 'free'. If it doesn't - I want to abort new user creation (and hopefully throw error client side)
There are 2 functions which I've found in the docs that presumably can help me do it:
Accounts.validateNewUser
Problem: it receives 'trimmed-down' user object which doesn't contain properties other than profile, username, password, email. Thus I cannot validate account.type as it doesn't exist on user object being validated.
Accounts.onCreateUser
Problem: it is called after a generic user object is created and there is no way I can cancel inserting new document in Users collection. It absolutely requires to return a user document. If I return undefined it throws errors on server:
Exception while invoking method 'createUser' Error: insert requires an argument
It also doesn't allow to throw method errors (as it's not a method) -> thus I cannot log error client side.
You can use Accounts.validateNewUser with little change to your data structure:
userData = {
profile: {
name: 'Artem',
account : {
type : 'free'
}
},
username: 'aaa#gmail.com',
password: '123'
};
Then you should be able to access data you need.
As far as I remember there were some discussion on meteor forum about removing profile field, that's why I'm solving this kind of problems in different way. For me Meteor.users is collection which should not be changed for sake of peace in mind - it could be changed by future version of meteor. My approach require to write more code in the beginning, but later it pays off, because you have place to store data about user and Meteor.users collection has docs with minimal amount of data.
I would use jagi:astronomy#0.12.1 to create schema and custom methods. In general I would create new collection UserAccounts with schema:
UserAccount = new Astro.Class( {
name: 'UserAccount',
collection: 'UserAccounts',
fields: {
'userId' : {type: 'string'},
'type' : {type: 'string', default:'free'}
},
} )
and add schema to Meteor.users :
User = new Astro.Class( {
name: 'User',
collection: Meteor.users,
fields: {
'services' : {type: 'object'},
'emails' : {type: 'array'}
},
methods:{
account : function(){
return UserAccounts.findOne({userId:this._id})
}
}
} )
The usage looks like this:
var user = Meteor.users.findOne();
user.account().type
In summary:
Accounts.onCreateUser : always allow to create user account and always create UserAccount which corresponds to it ( with field userId)

Resources