NGRX: Composing state when we have to represent data from two lists - ngrx

Practical example: Consider an expand/collapse list. Each item expands another list.
export interface MainDomainList {
id: number
name: string;
}
export interface SubDomainList {
id: number
name: string;
}
export interface AppState {
mainDomainList: MainDomainList[];
subDomainList: SubDomainList[];
}
On the UI the list should be represented like this:
MainDomainList[1]
SubDomainList[] (entire list)
MainDomainList[2]
Another SubDomainList[] (entire list)
etc..
When the user clicks on the MainDomain[n] there is a call to the backend which returns a list of SubDomain[]. There are no connections between the two of them.
It seems that the most complicated part is that the SubDomains are being loaded one by one on click not all at once, and multiple MainDomains can be open at the same time like in the example above. Also, it should be possible to easily perform CRUD operations on the subDomainList entities.
I tried using a selector which selects an item from the state by id but every time the state is overridden.
My initial idea was to create a separate state in which after the SubDomainList[] is loaded successfully, then I could add the loaded SubDomainList[] by dispatching an 'ADD' action thus adding the entities and the id of the clicked MainDomainList in the newList state as the user clicks on through the list obtaining something like this:
exportt interface AppState {
mainDomainList: MainDomainList[];
subDomainList: SubDomainList[];
newList: NewList[];
}
{
mainDomainList : {
entities: {
md1: {
id: 'md1',
name: '1'
},
md2: {
id: 'md2',
name: '2'
}
}
},
subDomainList : {
entities : {
sd1 : {
id : 'sd1',
name: 'name1'
},
sd2 : {
id : 'sd2',
name: 'name2'
}
},
newList : {
entities : {
md1 : {
id : 'md1',
subDomainList: [{}, {}]
},
md2 : {
id : 'md2',
subDomainList: [{}, {}]
}
}
}
}
Then somehow i would get all the newList entities and match them in the UI with the id of the MainDomainList[n].id
Is my approach correct or is there any other better or less complicated solution for this issue?
I'm fairly new to the subject but I had a lot of headaches trying to figure out how to implement this with ngrx/Entity and failed so far, although it should be a pretty common case. Any help would be much appreciated.

You can write selectors with argument by passing of main domain list
ref: ngrx parameter to select function
and https://blog.angularindepth.com/ngrx-parameterized-selector-e3f610529f8

Related

Vue3 reactivity and proxy objects behaving apparently random

I am having difficulties with Vue 3 reactivity and proxy objects. I am using Option API and I have a Vue Component that on button click execute following code
submitUser() {
let userCreated;
userCreated = {
id: this.user_tomodify.id,
firstName: this.firstName,
lastName: this.lastName,
email: this.email,
username: this.username,
};
this.$emit("onuserSubmitted", userCreated, this.usertype);
this.hide();
},
Now the code just create an object starting from the data of the component, firstName lastName etc are all v-model of input elements. The method called in the parent component emitting "onuserSubmitted" is the following
addUser(new_user, u_type) {
this.users[u_type].push(new_user);
MevoLogger.debug(this.users[u_type]);
}
Where this.users is an Object that associated to the field contained in u_type variable some arrays. For better undestanding this is the computed property:
users() {
switch (this.role) {
case "ROLE_SECRETARY":
return {
STUDENT: this.students,
TEACHER: this.teachers,
};
case "ROLE_TECHNICIAN":
return {
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
};
case "ROLE_ADMIN":
return {
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
TECHNICIAN: this.technicians,
ADMIN: this.admins,
};
And i have a list in the page rendered with v-for = user in users[type]
The problem I am having is that apparently without any reason, when I push new_user in the array sometimes Vue create a normal object and sometimes a Proxy object ( i checked this printing in the browser console the users[u_type] array so i'm sure that this is the problem, literally randomly i see sometimes added a Proxy while sometimes a normal {} object), in the first case reactivity is not triggered and so i don't see in the page the new item added, in the second case reactivity works and page is updated. How is that even possible? What can I do in order to make it always create Proxy Objects?
If users is a computed property - it is readonly by design so if you somehow change it, its value will be recalculated/rebuilt on the next re-render. Or it might not change at all. You should push to the underlying array(s) - not to the computed property. E.g. push to students, teachers, etc.
For example:
addUser(new_user, u_type)
{
{
STUDENT: this.students,
TEACHER: this.teachers,
SECRETARY: this.secretaries,
TECHNICIAN: this.technicians,
ADMIN: this.admins,
}[u_type].push(new_user);
}

Best way to make ReactiveAggregate reactive when data changes on a user

I am currently using ReactiveAggregate to find a subset of Product data, like this:
ReactiveAggregate(this, Products, [
{ $match: {}},
{ $project: {
title: true,
image: true,
variants: {
$filter: {
input: "$variants",
as: "variant",
cond: {
$setIsSubset: [['$$variant.id'], user.variantFollowing]
}
}
}
}}
], { clientCollection: 'aggregateVariants' }
As you can see, a variant is returned if user.variantFollowing matches. When a user 'follows' a product, the ID is added to their object. However, if I understand correctly, this is not triggering ReactiveAggregate to get the new subset when this happens. Only on a full page refresh do I get the correct (latest) data.
Is this the correct way to approach this?
I could store the user's ID as part of the Product object, but the way this would be stored would be nested two places, and I think I would need the Mongo 3.5 updates to then be able to accurately update this. So i'm looking for how to do this in Meteor 1.5+ / Mongo 3.2.12
So, I've been able to get there by adding autorun to the subscription of the aggregate collection, like this:
Template.followedProducts.onCreated(function() {
Meteor.subscribe('products');
this.autorun(() => {
Meteor.subscribe('productsFollowed');
});
... rest of function
For context, productsFollowed is the subscription to retrieve aggregateVariants from the original question.
Thanks to robfallows in this post: https://forums.meteor.com/t/when-and-how-to-use-this-autorun/26075/6

Meteor Framework Subscribe/Publish according to document variables

I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.

Sails js 2:n relation

I recently started working with SailsJS, and I found the process of defining attributes and associations very elegant. However, there is one thing I cannot find out:
I have a model Couple(person1, person2). I'd like to create an association in Person, that lists every couple they are a member of. Something along the lines of:
attributes{
//other attributes,
couples:{
collection: 'couple',
via: 'person1, person2'
}
}
My current method is:
attributes{
//other attributes,
couples1:{
collection: 'couple',
via: 'person1'
},
couples2:{
collection: 'couple',
via: 'person2'
},
getCouples: function() {
return this.couples1 + this.couples2;
}
}
But this doesn't seem very pretty, is there a way to do it more elegantly?
If i get your question correctly, you want a person and its couple, i'm assuming you have a monogamous relationship in your couples (meaning one person belongs to the other and vice-versa) you can make an association with the same model, like this.
var Person = {
attributes: {
name: {type:'string'},
couple: {model: 'Person', via: 'couple'}
}
};
module.exports = Person
Then you can create and associate them like this. (Assuming sails console)
Person.create({name: 'John Doe', couple: {name: 'Jane Doe'} }).exec(console.log)
Person.update(2, {couple:1}).exec(console.log)
And retrieving them is as easy as
Person.find({name:'John Doe'}).populate('couple').exec(console.log)
You can try to associate them with .add and .save but i'm not sure if it works when the association points to the same model, so check that out.
Another option is to have it like this:
var Person = {
attributes: {
name: {type:'string'},
couple: {model: 'Couple', via: 'persons'}
}
};
module.exports = Person;
Couple model holds a collection of persons, so you can add persons to the
couple model, and associate them later with the person.couple attribute.
var Couple = {
attributes: {
persons: {collection: 'Person', via: 'couple'}
}
};
module.exports = Couple;
Or you could use what you have, which is not ideal in this case, but it works.

Different priority based on user in Firebase

I have data which looks like this:
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill"
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill"
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John"
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack"
}
When a user is logged in with id simplelogin:5 I want to order the output based on their sort preferences. So say for example user simplelogin:5 previously set his order to Jack,Jill,Joh,Bill and simplelogin:1 set their order to Bill,John,Jack,Jill.
I know I can set priority but that's priority for the data as a whole and it isn't tied to a user, this is shared data which needs custom priority per user.
I was thinking of setting up something like this:
users[
{
"uid":"simplelogin:1",
"nameOrder":[-gn643Jp3rgsIeM2O0EN, -yryexaJp3rgsIeM2O0EN, etc.]
}
];
But it seems like there should be a better way, and even if I was able to generate a list like that, i'm not sure how to sort the output to follow the order in the nameOrder entry.
First you need to modify your main list with a persistent sort id. I used a numeric value but you can use whatever value you prefer.
"-JnbxaJp3rgsIeM2O0EN" : {
"Name":"Bill",
nameIndex": 0
},
"-yryexaJp3rgsIeM2O0EN" : {
"Name":"Jill",
"nameIndex": 1
},
"-6yrhxaJp3rgsIeM2O0EN" : {
"Name":"John",
"nameIndex": 2
},
"-gn643Jp3rgsIeM2O0EN" : {
"Name":"Jack",
"nameIndex": 3
}
Then store your user defined sort preference
"simplelogin:1" : {
"nameOrder": "0,2,3,1"
},
"simplelogin:5" : {
"nameOrder": "3,1,2,0"
}
Now when you read the name list, use the array index saved in nameOrder to display results.

Resources