I am trying to figure it out, how should I properly create a JSON model for a React component state.
Since now I always used Entity Framework and virtual properties to connect related "tables", so now when I want to do similar thing in React and JSON I have don't really know how to proceed.
This is my simplified model:
{
"myModel": {
"Categories": [
{
"Id": 1,
"Name": "Cat1",
"Active": true
},
{
"Id": 2,
"Name": "Cat2",
"Active": false
}
],
"Components": [
{
"Id": 1,
"Name": "Component1",
"CategoryId": 1
},
{
"Id": 2,
"Name": "Component2",
"CategoryId": 1
},
{
"Id": 3,
"Name": "Component3",
"CategoryId": 2
}
]
}
}
How to effectively join these two "tables"?
For example if I want to filter Components, whose Category is Active?
In my second approach I changed model to contain the whole Category object in Component:
..."Components": [
{
"Id": 1,
"Name": "Component1",
"Category": {
"Id": 1,
"Name": "Cat1",
"Active": true
}
},...
This allowed me to use filter(a=>a.Category.Active==true) function very easily, but then the problem is when I make changes to a property of one of the Categories, the change does not reflect to Components.
What is the best approach in this situation? Is is better to update all Component[].Category on each Category change or loop through all Categories to find the correct one each time I need to filter or group Components on CategoryId?
I need to have Categories in separate array, because they are not always all in use by Components.
You can easily aggregate the data and filter for active Components by using your data structure:
const activeComponents = myModel.Components.filter(component => {
let isActive = false;
const componentCategory = myModel.Categories.filter(
category => category.Id === component.CategoryId
);
if (componentCategory.length && componentCategory[0].Active)
isActive = true;
return isActive;
});
You can also shorten the code in case there is always a Category for each CategoryId:
const activeComponents = myModel.Components.filter(
component =>
myModel.Categories.filter(
category => category.Id === component.CategoryId
)[0].Active
);
You should check out the redux docs for that. You should not duplicate data and keep it as flat as possible. So your second approach is not advisable, because it both duplicates and nests the data. The components should be inserted into an object where the keys are the ids. Additionally, you could keep all active components in an string array, which contains all active component ids and and retrieve them by iterating over the active component array and extracting the component with the id from the mapping object.
Related
I am fetching some data via an API call (within my dataservicse.ts file) - and the response I get is a sort of complex JSON structure. I am able to fetch a specific part of the response as follows in my corresponding Component file as follows -
Here is one of the JSON object response that represents the general structure -
{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}
Now - my goal is to acquire the 'name' attribute as well as the first 'grade' value within the grades attribute from each response object - and map them into separate Arrays so that I can display them in table columns using *ngFor.
This is my Component code
export class TestDataComponent implements OnInit {
name$: Object;
grade$: Object;
constructor(private data: DataService, private data2: DataService) { }
ngOnInit() {
//getAPIData is returning the API response from the dataservices.ts file
this.data.getAPIData().subscribe(
data=>console.log(data.response.map(name=>name.name))
); //this works fine - I get the names in an array
this.data2.getAPIData().subscribe(
data2=>console.log((data2.response.map(grade=>grade.grades)).map(grades
=> {grades.map((value, index) => value.grade})) //this returns an undefined value
);
}
Now - if I console.log((data2.response.map(grade=>grade.grades))
I get an Array of Array objects such as -
Array - [Array(3), Array(3), Array(2)]
and each of them consist of the 'grades' attribute Array of objects.
(taking the first array from above) -
Array(3)
0:{"grade": "A","score": 3}
1:{"grade": "B", "score": 4}
2:{"grade": "A", "score": 2}
Thus - I further map my initial response to achieve the 'grade' value. Also - I only want the first grade - thus I have a simple condition added as follows -
console.log((data2.response.map(grade=>grade.grades))
.map(grades
=> {grades.map((value, index) =>{if(index<1) value.grade})}))
As mentioned in the comment - I get an undefined value.
I undestand this issue may be fairly complex but I've tried my best to explain it as clearly as possible. My goal is to get the first 'grade' values from each object and display them in an Array - just as the names, so that I can use it to display it on a table.
I am fairly new to Angular6, just switching from Angular 1.6 - so I am pretty sure I am messing something up.
What would be the best way to get the grade values into an Array by nested mapping within subscribe? Or is there a better approach to the same ?
Also - for the sake of simplicity, ignore the fact that the first subscription is present (for the name attribute) - I showed it here so as to make it clear as to what I want to achieve.
Here is what I think you're asking for since you never gave a concrete example of what you're trying to map/reduce. Also, this is vanilla JavaScript but could easily be translated into RxJS.
// I am assuming that a call to `DataServce.getAPIData()` returns an array
const api_response = [{
"address": {
"building": "1234",
"coord": [0, 0],
"street": "123 Main Street",
"zipcode": "00000"
},
"address2": "Test Suite 000",
"grades": [{
"grade": "A",
"score": 3
}, {
"grade": "B",
"score": 4
}, {
"grade": "A",
"score": 2
}],
"name": "John Doe",
"_id": "1212121"
}];
// Map and pluck the values you need
const result = api_response.map(v => [v.name, v.grades[0].score])
// Or as an array of objects
const obj_result = result.map(([name, score]) => ({ name, score }))
// Is this what you want?
console.log('Array', result);
console.log('Objects', obj_result);
Quick Update
Thanks for accepting the answer. Just wanted to give a quick of what this might look like using RxJS. I say might because the code snippet below is untested.
this.nameAndScores$ = data.getAPIData().pipe(
map(({ response }) => response),
map(({ name, grades }) => ( { name, score: grades[0].score } ))
)
Assuming that nameAndScores$ is a property of your component instance, you can do *ngFor item in nameAndScores$ | async and avoid any explicit subscriptions in your code.
You are using curly braces with a fat arrow in second map function. While using curly braces, you should return a value using the return keyword.
I have an array of objects as below. I am trying to achieve search functionality using .filter() method in my angular2 application.
[{
"label": "new",
"result": [{
"label": "new",
"fname": "abc",
"lname": "xyz"
}, {
"label": "new",
"fname": "abc1",
"lname": "xyz1"
}]},
{
"label": "old",
"result": [{
"label": "old",
"fname": "abc2",
"lname": "xyz2"
}]
}]
I am able to achieve parent/one level filtering using below code:
this.data.filter(item => (item.label.toLowerCase().indexOf(inputText) !== -1);
This is returning the object that matches value of label. I want to have filter on 'fname' and 'lname' also.
Your data structure is not well suited for doing an arbitrary string lookup from a 3 level data structure like this.
Your are going to end up having to iterate all three levels - the array of objects, the results array in each object, and all the properties of the objects in the results array.
This is an O(n^3) operation and would likely perform unsatisfactorily with a large data set. You may need to think about how you can better structure this data for this use case.
I would when I get the data from the server create a lookup variable inside every item which contains all text you want to be able to search.
But make sure this is only done once, since it's wouldn't be very performant if you did it for every keystroke.
// Add searchable string to users
users.forEach(u => {
u.filterTerm = `${(u.id || '').toLowerCase()} ${(u.email || '').toLowerCase()} ${(u.firstName || '').toLowerCase()} ${(u.lastName || '').toLowerCase()}`;
});
Then you can use the includes method to filter
let filtered = allUsers.filter(u => {
return u.filterTerm.includes(filterTerm);
});
Up until now I've been using .get() and _.clone() on the array attributes then making my object up before setting the model but this feels completely wrong and I'm not sure how to improve upon this. I feel the array attributes should be transformed into collections but 1) I'm not sure how to do this and 2) I'm not sure of the real benefits compared to my current approach. Can anyone help me improve on my approach? Also note, the POST object needs to be created to be sent via AJAX to a non RESTful service
Example model:
{
"id": 1,
"name": "Joe Bloggs",
"teams": [],
"ageGroups": [],
"categories": []
}
Example of how my data should look when posting back to server
{
"id": 1,
"name": "Joe Bloggs",
"teams": [
{
"id": 123,
"name": "Team One",
"location": "UK"
}, {
"id": 321,
"name": "Team Two",
"location": "USA"
}
],
"ageGroups": ["16", "18", "21"],
"categories": [
{
"id": 45,
"name": "Category One"
}, {
"id": 65,
"name": "Category Two"
}
]
}
A very stripped down example:
var myView = new View({
addToCategory: function() {
var categories = _.clone(this.model.get('categories'));
// Grab values I need from user input...
var categoryDetails = {
"id": userId,
"name": userName
};
this.model.set({
categories: categoryDetails
});
},
addToAgeGroups: function() {
var ageGroups = _.clone(this.model.get('ageGroups'));
// Grab my age group via user input ...
ageGroups.push(newAgeGroup);
this.model.set({
ageGroups: ageGroups
});
}
});
Backbone (intentionally?) does not handle nested data particularly well. After some experience I've resisted the urge to make my Model have an attribute on it that is a Collection, etc. It generally makes things more confusing for little benefit.
I suggest making your Models smarter to handle the array attributes, moving some of the logic in your View into the Model itself. If you really think some parts of your application would like to treat that data as a full-blown Collection, have the Model handle that internally too.
For instance:
var MyModel = Backbone.Model.extend({
getCategories: function() {
if (!this._categoriesCollection) {
var categories = this.get('categories');
this._categoriesCollection = new Backbone.Collection(categories);
}
return this._categoriesCollection;
}
addCategory: function(categoryDetails) {
var currentCategories = this.getCategories();
currentCategories.add(categoryDetails);
}
});
Caching the result of getCategories() means you can ensure there is only ever one instance of the collection of categories. You can add more methods to handle the Parent/Child relationship within the Model, making him the sole owner of that data.
Handling how to POST the data to server seems like a separate question, but generally I've overridden the Model.sync() method to do that sort of thing.
I have this list i wanted to display on ng-table.
$scope.list = [
{
"moduleId": 1,
"name": "Perancangan",
"level": 0,
"childs": [
{
"moduleId": 12,
"name": "Perancangan Sektor",
"level": 1,
"childs": [],
"links": [
{
"rel": "self",
"href": "http://103.8.160.34/mrf/modules/1"
}
]
}
]
},
{
"moduleId": 2,
"name": "Pengurusan Pengguna dan Peranan",
"level": 0,
"childs": [
{
"moduleId": 17,
"name": "Pengurusan Pengguna",
"level": 1,
"childs": [],
"links": []
},
{
"moduleId": 18,
"name": "Operasi Peranan",
"level": 1,
"childs": [],
"links": []
}
],
"links": [
{
"rel": "self",
"href": "http://103.8.160.34/mrf/modules/2"
}
]
}
];
I wanted the list.childs to be the rows in the table with the list.name as grouping, i'd used ng-repeat to but doesn't work. The most i could do is display it as td. What im looking at is
Perancangan (header)
Perancangan Sektor
Pengurusan Pengguna dan Peranan
Here is the plunker
http://plnkr.co/edit/77t5id3WOmbl2GSqYKh2?p=preview
There seems to be at least two ways you could accomplish this. The first might be more simple but skirts the question you posed. Massage the list[] into a flattened and simplified array tailored for this table's view. You would then ng-repeat over that array.
That is really a dodge though and completely avoids your question. More directly to your question you could try to use nested ng-repeat's but those are pretty tricky. See: http://vanderwijk.info/blog/nesting-ng-repeat-start/
Finally, the approach that seems to best address you're question in both intent and spirit is to use a custom filter. I've written an example fiddle that should demonstrate the idea.
app.filter('flatten', function() {
// Because this filter is to be used for an ng-repeat the filter function
// must return a function which accepts an entire list and then returns
// a list of filtered items.
return function(listItems) {
var i,j;
var item, child;
var newItem;
var flatList = [];
// Begin a loop over the entire list of items provided by ng-repeat
for (i=0; i<listItems.length; i++) {
var item = listItems[i];
// Construct a new object which contains just the information needed
// to display the table in the desired way. This means we just extract
// the list item's name and level properties
newItem = {};
newItem.name = item.name.toUpperCase();
newItem.level = item.level;
// Push the level 0 item onto the flattened array
flatList.push(newItem);
// Now loop over the children. Note that this could be recursive
// but the example you provided only had children and no grandchildren
for (j=0; j<item.childs.length; j++) {
child = item.childs[j];
// Again create a new object for the child's data to display in
// the table. It also has just the name and level.
newItem = {};
newItem.name = child.name;
newItem.level = child.level;
flatList.push(newItem);
}
}
// Return the newly generated array that contains the data which ng-repeat
// will iterate over.
return flatList;
};
});
How to refer to each property of an object in an array of objects in MongoDB MapReduce JavaScript query?
Here is my data:
{
"_id": ObjectId("544ae3de7a6025f0470041a7"),
"name": "Bundle 4",
"product_groups": [
{
"name": "camera group",
"products": [
{
"$ref": "products",
"$id": ObjectId("531a2fcd26718dbd3200002a"),
"$db": "thisDB"
},
{
"$ref": "products",
"$id": ObjectId("538baf7c26718d0a55000043"),
"$db": "thisDB"
},
{
"$ref": "products",
"$id": ObjectId("538baf7c26718d0a55000045"),
"$db": "thisDB"
}
]
},
{
"name": "lens group",
"products": [
{
"$ref": "products",
"$id": ObjectId("531e3ce926718d0d45000112"),
"$db": "thisDB"
},
{
"$ref": "products",
"$id": ObjectId("531e3ce926718d0d45000113"),
"$db": "thisDB"
}
]
}
]
}
Here is my map function: (for simplicity I took out the reduce option since it doesn't matter if the map doesn't work right)
var map = function() { emit(this.product_groups, this.product_groups.products); };
db.instant_rebates.mapReduce(
map,
{
out: "map_reduce_example",
query: {"_id": ObjectId("544ae3de7a6025f0470041a7")}
}
);
However the problem is that the "value" field in the result always comes up as "undefined". Why? Why doesn't this.product_groups.products return the products array? How do I fix this?
Also, I want it to do is to emit TWICE, once for each of the two product_groups. But so far it only emits ONCE. How do I fix that?
Under mapReduce operations the documents are presented as JavaScript objects so you need to treat them as such and traverse them. That means processing each member of the array:
var map = function() {
this.product_groups.forEach(function(group) {
emit( group.name, { products: group.products } );
});
};
var reduce = function(){};
db.instant_rebates.mapReduce(
map,
reduce,
{
out: "map_reduce_example",
query: {"_id": ObjectId("544ae3de7a6025f0470041a7")}
}
);
The requirements of the "emit" function is both a "key" and a "value" to be presented as arguments which are emitted. The "value" must be singular therefore to emit an "array" of data you need to wrap this under the property of an object. The "key" must be a singular value as it's intent to it be used as the "grouping key" in the reduce operation, and the "name" field should be sufficient at least for example.
Naturally since there is a top level array in the document you process "each element" as is done with the function, and then each result is "emitted" so there are "two" results emitted from this one document.
You also need to at least define a "reduce" function even if it never gets called because all of the emitted keys are different, as is the case here.
So, it's JavaScript. Treat a list structure as a list.
Please note. This is all your question is about. If you want to ask further questions on mapReduce then please ask other questions. Don't ask any more of this one. I don't want to talk about your field naming, or go into detail of how this seems to be working towards "how do I pull in data from the other collection", which is something you cannot do.