I have a collection that looks like:
name: {
type: String,
maxlength: 150,
required: true
},
description: {
type: String,
maxlength: 350
},
status: {
type: String,
default: 'active'
},
targets: [ {
type: Schema.Types.ObjectId,
ref: 'Thing',
index: true
} ]
});
The problem is with targets. Creating and adding to that array is no problem. However, if I reduce the number of elements in the array, it updates the targets, but does NOT reduce the size of the array, which causes numerous problems.
For example if targets = ["111111111111111111111111", "222222222222222222222222", "333333333333333333333333"]
and I do an update with targets = ["111111111111111111111111", "333333333333333333333333"],
the resulting array is ["111111111111111111111111", "333333333333333333333333", "333333333333333333333333"] since it doesn't reduce the size of the array.
I've looked at numerous things, and can't figure this out. The actual targets in my case can have several hundred elements. Also, doing an $addToSet doesn't seem to work, as it still won't remove the extra elements at the end. I really can't do a $slice, either - at least I haven't figured-out a way to do that. When I tried, I got an error saying that I couldn't update the same field twice.
How does one do this?
Here is the update code:
let filter = {
_id: aRecord._id
};
let update = aRecord;
MyCollection.findOneAndUpdate(filter, update, (err, insertStatus) => {
if (err) {
console.error(err);
return next(err);
}
if (1 === insertStatus.ok) {
res.status(200);
}
return res.json(insertStatus);
});
Thanks!
Seems stupid, but this works when reducing number of array elements of an array field:
{ $push:{ targets: { $each: sourceArray, $position: 0, $slice: sourceArray.length } } };
Basically, insert the array of elements in the front, then truncate the array to the length of the source array.
This assumes the source array has entire list of array elements. So, the front-end, user changes the number of checkboxes in a list - it sends the entire list of checkboxes, not a delta.
Related
I am having trouble with writing the following method on an Angular class. I don't know how to add values from arrayId to the data array in the series object.
getChartOptions() {
const arrayId=[];
const arrayTimestamp=[];
const arrayData=[];
const arrayData2=[];
var i=0;
this.httpClient.get<any>('http://prod.kaisens.fr:811/api/sleep/?deviceid=93debd97-6564-454b-be33-35bd377a2563&startdate=1612310400000&enddate=1614729600000').subscribe(
reponse => {
this.sleeps = reponse;
this.sleeps.forEach(element => { arrayId.push(this.sleeps[i]._id),arrayTimestamp.push(this.sleeps[i].timestamp),arrayData.push(this.sleeps[i].data[18]),arrayData2.push(this.sleeps[i].data[39])
i++;
});
console.log(arrayId);
console.log(arrayTimestamp);
console.log(arrayData);
console.log(arrayData2);
}
)
return {
series: [{
name: 'Id',
data: [35, 65, 75, 55, 45, 60, 55]
}]
}
}
I have two main pieces of advice for you:
Know the types of that data that you are dealing with.
Get familiar with all of the various array methods.
get<any>() is not a helpful type. If you understand what the response is then Typescript can help ensure that you are handling it correctly.
I checked out the URL and it looks like you get an array of objects like this:
{
"_id": 4,
"device_id": "93debd97-6564-454b-be33-35bd377a2563",
"timestamp": 1612310400000.0,
"data": "{'sleep_quality': 1, 'sleep_duration': 9}"
},
That data property is not properly encoded as an object or as a parseable JSON string. If you control this backend then you will want to fix that.
At first I thought that the data[18] and data[39] in your code were mistakes. Now I see that it as attempt to extract values from this malformed data. Accessing by index won't work if these numbers can be 10 or more.
The type that you have now is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: string;
}
The type that you want is:
interface DataPoint {
_id: number;
device_id: string;
timestamp: number;
data: {
sleep_quality: number;
sleep_duration: number;
}
}
You can type the request as this.httpClient.get<DataPoint[]>( and now you'll get autocomplete on the data.
It looks like what you are trying to do is basically to convert this from one array of rows to a separate array for each column.
You do not need the variable i because the .forEach loop handles the iteration. The element variable in the callback is the row that you want.
this.sleeps.forEach(element => {
arrayId.push(element._id);
arrayTimestamp.push(element.timestamp);
arrayData.push(element.data[18]);
arrayData2.push(element.data[39]);
});
The .forEach loop that you have now is efficient because it only loops through the array once. A .map for each column is technically less efficient because we have to loop through separately for each column, but I think it might make the code easier to read and understand. It also allows Typescript to infer the types of the arrays. Whereas with an empty array you would need to annotate it like const arrayId: number[] = [];.
const mapData = (response: DataPoint[]) => {
return [{
name: 'Id',
data: response.map(element => element._id)
}, {
name: 'Timestamp',
data: response.map(element => element.timestamp)
}, {
name: 'Sleep Quality',
data: response.map(element => parseInt(element.data[18])) // fix this
}, {
name: 'Sleep Duration',
data: response.map(element => parseInt(element.data[39])) // fix this
}]
}
The HTTP request is asynchronous. If you access your array outside of the subscribe callback then they are still empty. I'm not an angular person so this part I'm unsure of, but I think that you want to be updating a property on your class instead of returning the value?
Just follow this piece of code:
series: [{
name: 'Id',
data: arrayId
}]
I am currently trying to loop through an array of objects (each object is a task), where each task contains relevant information such as a name and date. Information from each task is then utilized in creating an object containing arrays, where each array contains objects that correspond to the date, or the array.
My current code is as follows:
contextTasks.forEach((taskItem) => {
taskItem["taskSchedule"].forEach((dateItem) => {
setItems((items) => ({
...items,
[dateItem["date"]]: [
{
name: taskItem["taskName"],
time: new Date(dateItem["time"]).toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
}),
type: "Task",
},
],
}));
});
});
However, if there are multiple tasks with the same date, they will override each other and I only end up with one task per date. How would I go about pushing further objects to the array if there are other entries for that specific date?
Finished object:
Object {
"2021-04-21": Array [
Object {
"name": "Test Class v1",
"type": "Class",
},
],
"2021-04-24": Array [
Object {
"name": "Test Task v2",
"type": "Task",
},
//I would like to add another object here without overriding existing contents of the array
],
}
Have you tried using reduce ?
the idea will be to have something like this inside your accumulator:
{"date1": [{val}, {val}, ...] , "date2": [{val}, {val}, ...]}
array.reduce((acc, val) => {
// test if your accumulator has the same date as your date from the val
if(acc[val.date]) {
acc[val.date] = [... acc[val.date], ...your val]
} else {
// no date found in the accumulator so make acc.date = ...acc.date, val
acc[val.date] = [ val ]
}
}, {})
Sorry if the code is not perfect but if you want provide your initial array of data and I will fix the response code
The cause of your issue is the fact you're executing an async method inside a synchronous loop. Also, modifying state forces a re-render, and you're attempting to do it presumably many times at once. It might, and will cause a bottleneck at some point.
The solution: build your new state first, and execute a setState once.
I am doing a small project in Angular8 and i have an array which contains data of users as object. This array is provided to me by the client which i can not change.
[
{
id:045#71,
name:'Ahmad',
isActive: false
},
{
id:047#71,
name:'John',
isActive: false
},
{
id:048#71,
name:'Doe',
isActive: false
}
]
In this array i have id's as custom indexes because of which i am not able to iterate this array. I am getting undefined in console when i try to iterate the array . Is there a way to iterate array with custom indexes. I even tried forEach loop but it is not working as well.
The method i used:
usersArray = [
{
id:045#71,
name:'Ahmad',
isActive: false
},
{
id:047#71,
name:'John',
isActive: false
},
{
id:048#71,
name:'Doe',
isActive: false
}
];
ngOnInit() {
this.usersArray.forEach((user)=>{
console.log(user.id + ' - ' + user.name);
})
}
What you have is what we typically call a collection. You can access an item from the collection if you know its index. In most cases, the index is an integer greater or equal to zero. i.e. collection[0] will give you the first item.
You can use collection.find(el => el.id ==='045#71') to find an element in the collection, if it doesn't exist will return undefined.
To find the index of an item you can use collection.findIndex(el => el.id ==='045#71'), in this case it will return 0
If you want to use customer indexes you would have to convert the collection into an object that uses your custom indexes as keys. There's surprisingly also a way to use custom keys in an array but it's not common practice in JavaScript.
I'm going around in circles with this one so hoping someone can help. I'm building a nodejs application that receives sensor values from nodes. There can be multiple sensors on a node.
Using NodeJS, Mongod DB and Mongoose, all running on a raspberry pi, 3 I've built the following Schemas & Model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var valueSchema = new Schema ({
timestamp: {type: Date},
value: {}
});
var sensorSchema = new Schema ({
id: {type: Number},
type: {type: String},
description: {type: String},
ack: {type: Boolean},
values: [valueSchema]
});
var SensorNode = mongoose.model('SensorNode', {
id: {type: Number, required: true},
protocol: {},
sensors: [sensorSchema]
});
I can add in the node, and push sensors onto the sensors array, but I seem unable to push values onto the values array.
I've looked over a few other examples and questions on similar issues, and looked at using populate, but cant seem to get them to work.
Here is my code:
function saveValue(rsender, rsensor, payload) {
var value = {
values: {
timestamp: new Date().getTime(),
value: payload
}
}
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$": value
}
}, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
}
This is returning undefined for the result and this error:
MongoError: exception: Cannot apply $push/$pushAll modifier to non-array
Values is definitely an array in the sensor schema.
I'm using readable ids rather than the auto assigned Mongo DB IDs for the sake of the UI, but I could use the MongoDB _id if that makes any difference, I don't see why it would?
Where am I going wrong ?
You're using positional operator $ so let's check the docs
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
So sensors.$ will return one particular document from your sensors array. That's why you're getting an error. On this level of your document you can only replace this item by using $set. I bet you wanted to do something like this:
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$.values": payload
}
});
This operation will just append payload to values array in one particular sensor with id equal to rsensor.
I have a collection Playlist that contains an array of items
{
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [
{
id: { // do not mix up with _id, which is the autogenerated id of the pair {id,type}. ID is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
}
]
}
Mongo automatically adds the _id field to the items when I push a pair {id,type} to items (but I don't care about it).
Now I would like to remove several "pairs" at once from the items array.
I have tried using $pullAll but it requires an exact match, and I do not know the _id, so it does not remove anything from items
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
I have tried using $pull with different variants, but it removed ALL objects from items
playlistModel.update({userId:userId},{$pull:{items:{"items.id":{$in:["123","456"]}}}},null,function(err){
playlistModel.update({userId:userId},{$pull:{items:{$in:[{id:"123",type:"video"},{id:"456",type:"video"}]}}},null,function(err){
Am I missing something or am I asking something that isn't implemented?
If the latter, is there a way I can go around that _id issue?
OK I found a way that works using $pull:
playlistModel.update({userId:userId},{$pull:{items:{id:{$in:["123","456"]}}}},null,function(err){
It doesn't take the type into account but I can't see any issue with that since the id is unique across all types anyway.
Although I will wait a bit to see if someone has a better solution to offer
EDIT
With Veeram's help I got to this other solution, which IMO is more elegant because I don't have _ids that I don't need in the database, and the $pullAll option seems more correct here
var playlistItemSchema = mongoose.Schema({
id: { // do not mix up with autogenerated _id. id is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
},{ _id : false });
var schema = new Schema({
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [playlistItemSchema]
});
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
tips:
you can use _id field to handle your playlistModel data.
mongoose api : new mongoose.Types.ObjectId to generate an Object_id
let _id=new mongoose.Types.ObjectId;
playlistModel.updateMany({_id:_id},{ $set: { name: 'bob' }}).exec(data=>{console.log('exec OK')});