I have a problem concating 2 json objects together. Basicly my app is doing a get on my rest server every second and i'm only sending the newest data back so as angular is refreshing the whole object i found on google that i can concat the 2 jsons together (old and new) so i can keep everything. But the problem is that none of the concat/merge/extend functions work and i don't know what i'm missing.
data: any = null;
constructor(private _http: Http) {
setInterval(() => this.getLogs(), 1000)
}
public getLogs() {
return this._http.get('http://localhost')
.map((res: Response) => res)
.subscribe(data => {
if data._body != ''{
//this.data = data.json()
if this.data == null
this.data = data.json();
else
extend(this.data,data.json()); // PROBLEM HERE
}
console.log(this.data);
});
}
So far i tried this.data.concat(data.json()); if i try extend(this.data, data.json()) or merge(this.data, data.json()); I get errors saying that it's not defined. The concat function doesn't do anything. Doesn't trigger errors neither concat so i don't know what it is doing.
I'm logging the object everytme and i can see the object always stays at the first ever response i get (meaning it only does the if this.data == null).
https://www.w3schools.com/jsref/jsref_concat_array.asp states
The concat() method is used to join two or more arrays.
This method does not change the existing arrays, but returns a new
array, containing the values of the joined arrays.
So you need to concat the two arrays into the data variable
data: any = null;
constructor(private _http: Http) {
setInterval(() => this.getLogs(), 1000)
}
public getLogs() {
return this._http.get('http://localhost')
.map((res: Response) => res)
.subscribe(data => {
if data._body != ''{
//this.data = data.json()
if this.data == null
this.data = data.json();
else
this.data = this.data.concat(data.json());
}
console.log(this.data);
});
}
You can use spread operator to generate new object:
this.data = {...this.data, ...data.json()};
What this does is create a new object and then first migrates all the fields and values from this.data and then same thing from data.json() while overriding any existing fields that were already in this.data.
Not sure where you're getting extend from. That's not a function.
You can't concat two objects together. You're calling res.json(), so the return is no longer JSON. Even if you were, you can't just concat JSON strings together and expect the result to be valid.
You'd want to merge the objects together, which can be done with Object.assign(this.data, data.json() or a spread: this.data = {...this.data, ...data.json()}.
On top of that, you'd want to try/catch your JSON parsing before assigning. Plus, your map function is doing literally nothing. You can parse it there instead.
You can also streamline this by just initializing data to an empty object.
public data: any = {}
public getLogs() {
return this._http.get('http://localhost')
.map(res => res.json())
.filter(res => !!res) // ensure data exists
.subscribe(data => {
Object.assign(this.data, data);
});
}
Having said that, making a REST call every second seems like an egregious waste of resources and will put strain on Angular's change detection, with performance degrading as data increases. If the objects don't need to be merged, i.e. each call is segmented data, consider pushing new data to an array instead of an object. Plus, you might want to consider doing something a little more sane, like implementing an event stream like SSE (server sent events) on the backend.
Related
I have the following function:
class RestService {
public async get<T>(func: string): Promise<T> {
var toRet = {};
await fetch(EndPoint + func)
.then(response => response.json() as Promise<T>)
.then(data => {
toRet = data;
})
.catch(e => {
});
return toRet as T;
}
}
Everything works fine but the response I get in 'data' is ALWAYS a generic object.
For example I might have a model like so:
class Model
{
string name;
}
and call the function like so:
get<Model>("getmodel")
The response is ALWAYS a generic object that looks like:
{name:"some name"}
From my understanding generics are supported in Typescript and Promise takes in variable types, my only thought is that I can't pass a generic into a generic?
Maybe a better way to write it would be this way.
class RestService {
public async get<T>(func: string): Promise<T | void> {
return await fetch('' + func)
.then(response => response.json() as Promise<T>)
.then(data => {
return data;
})
.catch(e => {
});
}
}
You can see it in the playground too at this link.
This way you don't have to overwrite any types and the compiler can figure out everything on it's own.
The return type is now Promise<T | void> because the catch function doesn't return anything. You could have something else or nothing depending what you do in case of an error.
Typescript will not transform the data object to match the T type you give in automatically.
If for example you call the method with get<AnotherModel>('modelEndpoint') but the endpoint returns Model. While the type at build time will say you should expect an object of type AnotherModel at runtime the object will in fact be of type Model.
This isn't clear from the question but maybe your issue is with the fact that the data is of type T instead of the Promise<T> that you return in the previous then callback.
If that is the case, that's because any Promise sent as a callback to the then function is resolved first before the outer then is called.
That means your code is equivalent to.
.then(response => response.json().then((data) => data as T))
.then(data => {
return data;
})
It's just that the Promise api will just take care of that for you.
If you want to learn more about the pitfalls of Promises in Javascript this post is quite good.
I realized a strange thing with my Angular2 typescript project. I have objects coming in from a webservice which have the type "Level" (it has the same properties as the Json coming from the webservice). In runtime comes out that the properties of the Level from the webservice have capital letters (Pascal case) at the beginning and the ones in my typescript project have small ones (visible in the browser's developer debug tool).
I guess I need to map the json properties somewhere somehow instead of doing a cast by writing "as Level[]" everywhere. How to I do it properly?
Update regarding the question that I should post some code:
(Controller)
ngOnInit(): void {
this.levelsObservable = this.levelsService.getAllLevels();
this.levelsObservable.subscribe(
data => console.log(data)
);
}
(Service)
observable : Observable<Response>;
getAllLevels(): Observable<Level[]> {
this.observable = this.achievementsService.getAllAchievements(this.allLevelsUrlPart);
return this.observable
.map((response: Response) => {
const srcData = response.json() as Level[];
return srcData;})
.catch(error => this.handleError(error));}
getAllAchievements(detailPath): Observable<Response> {
// prepare request url and header
this.specificUrl = this.webServiceUrl + detailPath;
this.headers.append('Content-type', 'application/json');
let options = new RequestOptions({ headers: this.headers });
this.result = this.http.get(this.specificUrl, options)
.catch(error => this.handleError(error));
return this.result;}
Update:
I polished my code a bit with the help of one answer below (not integrated above because not essential to solve the main problem).
I tried to use the other answer from below to reach the camel cases but it wasn't working (I have an array and in the array are objects with properties, but an object's properties aren't accessible with iterator methods).
Update:
I finally managed it (!) :) I shortened this post a bit and will now post my solution below. It's for sure not the most beautiful, but I'm happy to have one after searching around for hours. Thanks to all people helping me with their great and input here!
You could use this to get the lowercased objects.
modifiedSrc(srcData){
let obj = {};
Object.keys(srcData).forEach((key)=>{
obj[key.uncapitalize()] = srcData[key];
})
return obj
}
String.prototype.uncapitalize = function() {
return this.charAt(0).toLowerCase() + this.slice(1);
}
Then you can return the modified data
getAllLevels(): Observable<Level[]> {
this.observable = this.achievementsService.getAllAchievements(this.allLevelsUrlPart);
return this.observable
.map((response: Response) => {
const srcData = response.json() as Level[];
return this.modifiedSrc(srcData);})
.catch(error => this.handleError(error));}
You have complicated both of your methods.Make it simple as
this.webServiceUrl = "http...." ; // your service end point address
this.headers.append('Content-type', 'application/json');
let options = new RequestOptions({ headers: this.headers });
// For all your error handling
private handleError(error: Response) {
console.log(error);
return Observable.throw(error.json().error || 'Internal Server error');
}
Your service method can use TypeCasting which will look like
getAllLevels(detailPath): Observable<Level[]> {
return this.http.get(detailPath, options)
.map((response: Response) => <Level[]>response.json())
.catch(this.handleError);
}
Your component should raise the request to your service as
ngOnInit() : void{
this._myService.getAllLevels()
.subscribe(levels => this.levels = levels,
error =>this.errorMessage =<any> error);
}
Your variable declaration must be like
levels:Level[];
So finally... I got a solution! For sure not the most beautiful one, but easy to understand and done with hard work and research:
private useLevelProperties (response: any): Level[]{
let levels: Level[] = [];
Object.keys(response).forEach((key) => {
//create a new object and just take out the json parts needed. The webservice retrieves Pascal case letters, so we
//need to convert them into camel case ones.
this.level = new Level(response[key]["AchievementId"], response[key]["Image"],
response[key]["GrantedTo"], response[key]["GrantedBy"], response[key]["GrantedWhen"], response[key]["Description"],
response[key]["Name"], response[key]["CompetitionCode"], response[key]["Number"]);
levels[key] = this.level;
});
return levels;
};
Another option is to do it server-side with an extra json option for camel case: example for server-side camel casing
I developped an Angular2 service to retrieve a list a categories from a backend server and count how many 'links' exist per category.
Once I have the number of links for each category, I add a property to the Json object to 'store' the value.
Here is the code:
nbLinks = '';
...
getCategories() {
return this.category.find({where: {clientId: this.userApi.getCurrentId()}}).map((data) => {
this.categoriesList = data;
for (var i = 0; i < this.categoriesList.length; i++) {
var obj = this.categoriesList[i].id;
this.category.countLinks(obj).subscribe((linksCount) => {
this.nbLinks = linksCount;
}, err => {
console.log(err);
});
}
return data;
},
err => {
console.log(err);
}
);
I am getting the categories in a json object with the correct 'where' clause.
I am looping on the Json to 'count' the number of link in this category.
My problem is that outside the for loop (getting out) the variable i is bigger than my Json length so the app is crashing.
My second problem is that I do not have the visiblity of this.nbLinks outside the for ... loop.
Thanks an Regards
I'm not sure I understand your code, but two things stand out:
1) It looks like you're mixing synchronous and asynchronous code. It cannot work.
Sync code: the for loop. Async code: the observable.
Instead, could you refactor your code to ONLY work with observables and chain all the operations? You can wrap any piece of data in an observable with Observable.from() or Observable.of().
For instance:
getCategories() {
const categories = this.category.find({where: {clientId: this.userApi.getCurrentId()}});
return Observable.from(categories)
.map(category => countLinksInCategory(category));
}
If countLinksInCategory() is an async operation, then have that function return an Observable, and use .mergeMap() instead of .map() in the code above.
2) Try avoiding setting an outside variable from within your observable
// This part is not ideal
Obs.subscribe(linksCount => {
this.nbLinks = linksCount;
});
I would suggest renaming getCategories() to getNumLinks() to reflect the role of the function. The only job of the Observable inside this function is to produce a value. Then, the consumer of the Observable can use that value (i.e. assign it, display it...).
In terms of code:
getNumLinks(): Observable<number> {
// Here, count the number of links - See example code above.
// Eventually, return an observable wrapping the final value.
}
Then, elsewhere in your code:
// This is where you assign the value returned by the Observable.
// Note that we are OUTSIDE the Observable now.
getNumLinks().subscribe(numLinks => this.nbLinks = numLinks);
Take the following TypeScript/Angular 2 code sample:
query(): Rx.Observable<any> {
return Observable.create((o) => {
var refinedPosts = new Array<RefinedPost>();
var observable = this.server.get('http://localhost/rawData.json').toRx().concatMap(
result =>
result.json().posts
)
.map((post: any) => {
// Assume I want to convert the raw JSON data into a nice class object with
// methods, etc.
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
refinedPosts.push(refinedPost);
})
.subscribeOnCompleted(() => {
o.onNext(refinedPosts);
})
});
}
Written out, the database is returning JSON. I want to iterate over the raw JSON and create a custom object, eventually returning to subscribers an Array<RefinedPost>.
The code works and the final subscribers get what they need, but I can't help but feel like I didn't do it the "Reactive Way". I cheated and used an external accumulator to gather up the elements in the Array, which seems to defeat the purpose of using streams.
So, the question is, is there a better, more concise, reactive way to write this code?
Answering my own question.
query(): Rx.Observable<any> {
return this.server.get('http://localhost/rawData.json').toRx().concatMap(
result =>
result.json().posts
)
.map((post: any) => {
var refinedPost = new RefinedPost();
refinedPost.Message = post.Message.toLowerCase();
return refinedPost;
}).toArray();
}
This removes the internal accumulator and the wrapped Observable. toArray() took the sequence of items and brought them together into an array.
I returned mongoose docs as json in this way:
UserModel.find({}, function (err, users) {
return res.end(JSON.stringify(users));
}
However, user.__proto__ was also returned. How can I return without it? I tried this but not worked:
UserModel.find({}, function (err, users) {
return res.end(users.toJSON()); // has no method 'toJSON'
}
You may also try mongoosejs's lean() :
UserModel.find().lean().exec(function (err, users) {
return res.end(JSON.stringify(users));
});
Late answer but you can also try this when defining your schema.
/**
* toJSON implementation
*/
schema.options.toJSON = {
transform: function(doc, ret, options) {
ret.id = ret._id;
delete ret._id;
delete ret.__v;
return ret;
}
};
Note that ret is the JSON'ed object, and it's not an instance of the mongoose model. You'll operate on it right on object hashes, without getters/setters.
And then:
Model
.findById(modelId)
.exec(function (dbErr, modelDoc){
if(dbErr) return handleErr(dbErr);
return res.send(modelDoc.toJSON(), 200);
});
Edit: Feb 2015
Because I didn't provide a solution to the missing toJSON (or toObject) method(s) I will explain the difference between my usage example and OP's usage example.
OP:
UserModel
.find({}) // will get all users
.exec(function(err, users) {
// supposing that we don't have an error
// and we had users in our collection,
// the users variable here is an array
// of mongoose instances;
// wrong usage (from OP's example)
// return res.end(users.toJSON()); // has no method toJSON
// correct usage
// to apply the toJSON transformation on instances, you have to
// iterate through the users array
var transformedUsers = users.map(function(user) {
return user.toJSON();
});
// finish the request
res.end(transformedUsers);
});
My Example:
UserModel
.findById(someId) // will get a single user
.exec(function(err, user) {
// handle the error, if any
if(err) return handleError(err);
if(null !== user) {
// user might be null if no user matched
// the given id (someId)
// the toJSON method is available here,
// since the user variable here is a
// mongoose model instance
return res.end(user.toJSON());
}
});
First of all, try toObject() instead of toJSON() maybe?
Secondly, you'll need to call it on the actual documents and not the array, so maybe try something more annoying like this:
var flatUsers = users.map(function() {
return user.toObject();
})
return res.end(JSON.stringify(flatUsers));
It's a guess, but I hope it helps
model.find({Branch:branch},function (err, docs){
if (err) res.send(err)
res.send(JSON.parse(JSON.stringify(docs)))
});
I found out I made a mistake. There's no need to call toObject() or toJSON() at all. The __proto__ in the question came from jquery, not mongoose. Here's my test:
UserModel.find({}, function (err, users) {
console.log(users.save); // { [Function] numAsyncPres: 0 }
var json = JSON.stringify(users);
users = users.map(function (user) {
return user.toObject();
}
console.log(user.save); // undefined
console.log(json == JSON.stringify(users)); // true
}
doc.toObject() removes doc.prototype from a doc. But it makes no difference in JSON.stringify(doc). And it's not needed in this case.
Maybe a bit astray to the answer, but if anyone who is looking to do the other way around, you can use Model.hydrate() (since mongoose v4) to convert a javascript object (JSON) to a mongoose document.
An useful case would be when you using Model.aggregate(...). Because it is actually returning plain JS object, so you may want to convert it into a mongoose document in order to get access to Model.method (e.g. your virtual property defined in the schema).
PS. I thought it should have a thread running like "Convert json to Mongoose docs", but actually not, and since I've found out the answer, so I think it is not good to do self-post-and-self-answer.
You can use res.json() to jsonify any object.
lean() will remove all the empty fields in the mongoose query.
UserModel.find().lean().exec(function (err, users) {
return res.json(users);
}
It worked for me:
Products.find({}).then(a => console.log(a.map(p => p.toJSON())))
also if you want use getters, you should add its option also (on defining schema):
new mongoose.Schema({...}, {toJSON: {getters: true}})
Try this options:
UserModel.find({}, function (err, users) {
//i got into errors using so i changed to res.send()
return res.send( JSON.parse(JSON.stringify(users)) );
//Or
//return JSON.parse(JSON.stringify(users));
}
Was kinda laughing at how cumbersome this was for a second, given that this must be extremely common.
Did not bother digging in the docs and hacked this together instead.
const data = await this.model.logs.find({ "case_id": { $regex: /./, $options: 'i' }})
let res = data.map(e=>e._doc)
res.forEach(element => {
//del unwanted data
delete element._id
delete element.__v
});
return res
First i get all docs which have any value at all for the case_id field(just get all docs in collection)
Then get the actual data from the mongoose document via array.map
Remove unwanted props on object by mutating i directly