Hack news API - fetch all news - javascript

i have a problem with https://github.com/HackerNews/API.
I need fetch all best news in Angular 7, on 30 news in first page(title, author...). How can i sent get request with next api?
This API show all Id of best story:
https://hacker-news.firebaseio.com/v0/topstories.json?print=pretty
This api show example of one story:
https://hacker-news.firebaseio.com/v0/item/8863.json?print=pretty
I try this:
loadItem(){
this.http.get(`https://hacker-
news.firebaseio.com/v0/item/${this.id}.json?print=pretty`).subscribe(data => {
this.items.push(data as any[]);
})
}
loadBestItems(){
this.http.get('https://hacker-news.firebaseio.com/v0/beststories.json?
print=pretty').subscribe(data => {
this.bestItems.push(data as any[]);
})
}
I need 30 best news on first page

This is a bit of a loaded question, but I think we can break it down into three main questions:
1. How do you limit the number of stories returned by the hacker news api?
Since the hacker-news data is exposed through the firebase API, lets refer to the firebase docs. As indicated here, we can use the limitToFirst and orderBy options together to limit the number of results. We can simply order by the key, so your request URL would end up looking something like this:
'https://hacker-news.firebaseio.com/v0/beststories.json?
print=pretty&orderBy="$key"&limitToFirst=30'
2. How do you chain HTTP requests in Angular (make a second request that depends on the result of the first)?
This can be achieved with the mergeMap rxjs operator. This operator allows you to map the values emitted by an observable to another observable. To simplify things, imagine your initial request was to only return a single id. We could then use mergeMap to map the id to a request for the full item.
If that endpoint existed at the path beststory.json, it would look something like this.like this:
this.http.get('https://hack...v0/beststory.json').pipe(
mergeMap((id) => this.http.get(`https://hack.../v0/item/${id}`))
).subscribe((data) => {
console.log('Story: ', data);
}
Since you need to map to multiple requests, however, we will need to introduce another operator, outlined in question 3.
3. How do you make multiple HTTP requests at the same time (make a request for each item in a list)?
This can be achieved with the forkJoin rxjs operator. This operator takes an array of observables, and emits an array of their values once they are all complete. In the context of your problem, the input is an array of requests (one for each id in the initial request), and the output would be a list of items. To simplify things again, lets assume you already have an array of ids sitting around. Issuing requests for each item in the list would look something like this:
let ids = [1, 2,...];
forkJoin(ids.map((id) => this.http.get(`https://hack.../v0/item/${id}`)).subscribe((stories) => {
console.log('Stories:', stories);
});
Putting it all together
Now that we know how to map the result of a request to another observable with mergeMap, and we know how to combine the results of multiple observables into one with forkJoin, we can use them together to achieve what you're looking for:
this.http.get('https://hack....v0/beststories.json?orderBy="$key"&limitToFirst=30').pipe(
mergeMap((ids) => forkJoin(ids.map((id) => this.http.get(`https://hack...v0/item/${id}`)))),
).subscribe((stories) => {
console.log('Stories:', stories);
});
Note that in the code snippets I have excluded part of the url and unrelated query params

Related

Insert documents in different collections by calling one API

Im doing a project where i call one api, this given API service will handle all the given data and split it to different collections.
Given example
service.js
async function create(params, origin) {
const { param1 , param2 , param3 } = params
const collection1 = new db.Collection1(param1)
await collection1.save()
const collection2 = new db.Collection2(param2)
await collection2.save()
const collection3 = new db.Collection3(param3)
await collection3.save()
}
My questions are:
What is the best practice? Should I create a general model schema that groups all the collections with the parameters, "param1,param2,param3", and insert it in the collection then call another function that splits all the values into Collection1,Collection2....
How can i handle if one collection insert throws an error, to delete all the previously inserted collections ? For example the values of param2 is not valid, but param1 and param3 are, how can i handle to delete the collection 1 and 3 and throw an error ? Whats the best way to achieve this?
Generally all the above examples are simplified, we are talking about at least more than 10 collections and more than 15 parameters.
Basically you are talking about having multiple route handlers for a single path.
Generally you should handle server-side validation & sanitation on the input data before inserting into the db and throw errors right away if rules don't match, so having to delete previous 2 collection inserts in case the 3rd one fails is not needed anymore.
Check out express-validator middleware for this aspect.
Ideally you should have one route handler per path for several reasons but I think the most common one is ease of maintenance and debugging(conceptually like separation of concerns). It's easier to execute a sequence of requests to different paths and eventually await the response from the first request to be used in the next request and so on(if that's the case). In my opinion you're just adding a layer of complexity not needed.
It might work if you develop alone as a full-stack, but if you have a team where you do the back-end and somebody else does the requests from the front-end and he encounters a problem it'll be much harder for him to tell you which path => handler failed, because you're basically hiding multiple handlers into a single one path => [handler1, halder2, handler3]. If you think about it, this behaviour is causing your second question.
Another thing is, what do you do if somebody needs to operate just a single insert from that array of inserts you're trying to do? You'll probably end up creating separate paths/routes meaning you are copying existing code.
I think it's better for chaining/sequencing different request from the front-end. It's much better and elegant, follows DRY, validation and sanitation is indeed easier to code and it gives the consumer of your api freedom of composition.

Observable fires only once

I decided to pick up this RxJS tutorial over the weekend to learn about reactive programming. The goal is to set up a simple page that generates an interactive list of users from the Github users API using Observables.
The list displayed is a subset of the total number of users retrieved (in my case 3 out of 30). The list needs to be refreshable (show a new set of users), and you need to be able to remove entries from it by clicking the 'remove' button on each respective entry.
I've set up a chain of Observables to define the behavior of the page. Some act as events to trigger processing, and some publish processed results for use in the app. This chain should dynamically cause my list to be updated. Currently, the default flow is like this:
Startup!
Suggestions refresh triggered! (this is where the data is retrieved)
30 new suggestions available!
List update triggered! (this is where I choose 3 users to display)
List updated! (at this point, the list is showing on the page)
A list update is triggered on startup, by refreshing the list and by removing something from the list. However, when I refresh the list, this happens:
Refresh button clicked!
Suggestions refresh triggered!
30 new suggestions available!
As you can see, the trigger to update the list of users is not set off. As I understand it, by emitting some value at the start of a stream, the rest of the stream should be executed consequently. However, this only seems to be happening the first time I run through the chain. What am I missing?
You can find a running version of my project here.
I think the issue is in the way userStream$ Observable is created.
Filtering users not closed and then taking the first 3 is something that can be done directly on the UserModel[] array passed into the pipe chain by displayEvents$ via filter and slice methods of Array.
If you do so, you remove the need of using the from function to create an Observable<UserModel> on which you then have to apply flatMap (which is currently better known as mergeMap) to apply finally toArray to transform it back into an Array of UserModel.
In other words you can simplify the code as in the following example, which as side effect solves the refresh problem.
this.userStream$ = this.displayEvent$.pipe(
map(users => users
.filter((user: UserModel) => !this.closedUsers.has(user))
.slice(0, this.numberOfUsers))
// flatMap((users: UserModel[]) => from(users))
// // Don't include users we've previously closed.
// , filter((user: UserModel) => !this.closedUsers.has(user))
// , take(this.numberOfUsers)
// , toArray()
, tap(() => console.log('List updated!'))
// Unless we explicitly want to recalculate the list of users, re-use the current result.
, shareReplay(1));
To be honest though I have not fully grasped why your original solution, which is a sort of long detour, does not work.

How to retrieve multiple keys in Firebase? [duplicate]

This question already has an answer here:
Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly
(1 answer)
Closed 6 years ago.
Considering this data structure:
{
"users":{
"user-1":{
"groups":{
"group-key-1":true,
"group-key-3":true
}
}
},
"groups":{
"group-key-1":{
"name":"My group 1"
},
"group-key-2":{
"name":"My group 2"
},
"group-key-3":{
"name":"My group 3"
},
"group-key-4":{
"name":"My group 4"
},
}
}
I could get the groups of a particular user with:
firebase.database().ref('users/user-1/groups').on(...)
Which would give me this object:
{
"group-key-1":true,
"group-key-3":true
}
So now I want to retrieve the info for those groups.
My initial instinct would be to loop those keys and do this:
var data = snapshot.val()
var keys = Object.keys(data)
for (var i = 0; i < keys.length; i++) {
firebase.database().ref('groups/' + keys[i]).on(...)
}
Is this the appropriate way to call multiple keys on the same endpoint in Firebase?
Does Firebase provide a better way to solve this problem?
Is this the appropriate way to call multiple keys on the same endpoint
in Firebase?
Yes, generally this is a good solution to retrieve every group this way.
Does Firebase provide a better way to solve this problem?
I don't think Firebase provides any other function/query that could help in this case.
Anyway, this could be improved saving the ref in a variable and looping on the keys of the object directly. Also, if you just need to retrieve those data once, you should use once() instead of on()
var groupRef = firebase.database().ref('groups/')
var data = snapshot.val()
for (var groupID in data) {
groupRef.child(data[groupID]).once(...)
}
Another way could be using Firebase's functions for the data snapshots, like forEach
snapshot.forEach(function(childSnapshot) {
groupRef.child(childSnapshot.key).once(...)
})
Yes, you are going in the right way. As written in this question firebase will pipeline your requests and you won't have to be concerned about performance and roundtrip time. As soon as your iteration starts you will be receiving firebase data. So keep in mind to handle it properly in your ui.
Another option, that will depends on how big your /groups data will grow, is to keep a snapshot (could be a $firebaseObject if you are using angularfire) of the entire /groups branch that will refresh whenever data changes. Then you just have to track this snapshot. But again, if you are planning to play with a big amount of groups your current solution is a better choice.
Also, I recommend you to take care of setting an on event for each group you retrieve. Keep in mind that the callback will be triggered whenever the data changes (depends on the setted event). Depending on your use case you should consider using .once("value" since it will trigger the data only once, making it more reliable, more performatic and will avoid handling callbacks when you are not expecting them.

Using the .find().fetch() from within a function in Meteor

I am making a project with Meteor and I'm having some issues trying to get data out of mongodb in JavaScript. I have the following in a function:
console.log(Time.find({today: "Saturday"}).fetch());
In my publish.js file on the server side I have the following:
Meteor.publish("time", function () {
var currentUserId = this.userId;
return Time.find({user: currentUserId});
});
And In my subscriptions file I have the following:
Meteor.subscribe("time");
This function gets called later down in the code but it returns an empty array. If I run this code in my browsers console it returns an array with 2 objects in it, which is correct. This leads me wondering if I can use the .fetch() function from within my code? As if I leave off the .fetch() it returns what looks like the usual giant object. My real problem is I need the data in the form that .fetch() gives it to me in. I think it's because the function gets triggered before the data gets a chance to load in, as if I switch out the .fetch() for a .count() it returns 0.
Is there any way around this or a fix?
Where are you you running that console.log?
There are a couple fundementals here that I believe you may have glossed over.
1 Pub / Sub
This is how we get data from the server, when we subscribe to a publication i becomes active and begins to send data, this is neither instant or synchronous, (think of it more like turning on a hose pipe), so when you run your console.log, you may not yet have the data on the client.
2 Reactive contexts
One of the fundamental aspects to building anything in meteor is its reactivity. and it helps to start thinking in terms of reactive and non reactive contexts. A reactive context is one that re-runs each time the data it depends on changes. Using an autorun (Tracker.autorun or this.autorun insdie a template lifecycle callback) or a template helper are good examples. By placing it in a template helper it will re-run when the data is available.
Template.Whatever.helpers({
items: function() {
// ...do your find here.....
}
});
As items is a reactive context, depending on the collection data, it re-run when that changes, giving you access to the data when the client has them.
3 Retrieving Non Reactive Data
Alternatively it is also possible to retrieve data non-reactively by using Meteor.call with a meteor method, and then doing something with the result, in the callback to the Meteor.call. Depending on what you're doing, Meteor.wrapAsync may also be your friend here.
a simple example (out of my head, untested) :
// on the server
Meteor.methods({
gimmeStuff: function() {
return "here is your stuff kind sir!";
}
});
// on the client
Meteor.call('gimmeStuff', function(err, result) {
if (err || !result) {
console.log("there was an error or no result!");
return false;
}
console.log(result);
return result;
});
4 Its Unlikely that you actually need ithe .fetch()
If you're working with this in a template, you don't need a fetch.
If you want this to be non-reactive you don't need a fetch
As one of the commenters mentioned, a cursor is just a wrapper around that array, giving you convenient methods, and reactivity.
5 Go Back to the Begining
If you haven't already, I would highly recommend working through the tutorial on the meteor site carefully and thoroughly, as it covers all of the essentials you'll need to solve far more challenging problems than this, as well as, by way of example, teach you all of the fundamental mechanics to build great apps with Meteor.

AngularJS $http in nested foreach

I imagine that a solution is going to involve some form of promises although I'm struggling to get my head around how I would implement promises in this scenario.
Essentially I have a service which returns $resource, I then use .query() in the controller to get the array of site contexts. The query callback function then passes the response to a $scope function ($scope.getTaskLists).
The getTaskLists function loops through the site contexts using angular.forEach(). For every loop iteration it will use $http to return any task lists within each site context. Using the .success() promise, each $http request calls another for each loop, this time looping through the $http response (task lists). For each task list, another $http request is called which gets the root folder as we need a parameter later on. The .success() promise for this $http call extracts the parameter and then calls $scope.getTaskItems, passing in various parameters including site context and list id.
The getTaskItems function then uses the above parameters to make an $http request to the list, which will return all the list items. The .success() callback here then loops through all the items and pushes the objects to the task scope.
Ultimately the structure resembles something along these lines:
- $resource.query() // get site contexts
- angular.forEach(sites)
- $http().success(... // get task lists
- angular.forEach(taskLists)
- $http().success(... // get root context
- $http().success(... // get task items
- angular.forEach(taskItems)
- $scope.tasks.push(taskItem) // push task item to $scope
What I need to do is run some code once all task items have been pushed to $scope.tasks. Is this possible using promises and is there a better way of streamlining the above code so that it isn't mangled spaghetti code consisting of ajax requests and loops?
Thanks
In my opinion your angular workflow is good. The main problem may be on the server side.
If you need ALL your nested resources. Juste expose it in your json.
In your case you have something like :
[
{"id":"1", "name":"siteContext1"},
{"id":"2", "name":"siteContext2"}
]
And probably this kind of structure for all your resources. But in your specific case your first call should meet all you needs instead of giving you only a few information about the resource.
You probably need something like this :
[
{
"id":"1",
"tasks":[{
"id":"1",
"rootContext": [{...}]
}],
"name":"siteContext1",
},
{
"id":"2",
"tasks":[{
"id":"2",
"rootContext": [{...}]
}],
"name":"siteContext2",
}
]
If you can't modify your API. Then you're actually doing it in a good way according to your API ... in my humble opinion.
Actually $http calls return promises. success() is a function fired when the promise is resolved.
Hope it helped you.
can't you change the back end called by this:
$resource.query() // get site contexts
to get you all the:
$http().success(... // get task items
you need ?
Otherwise it seems to me to be a classical flow/usecase of the promises.

Categories

Resources