So I have an Angular component.
With some array objects containing data I want to work with:
books: Book[] = [];
reviews: Review[] = [];
This is what my ngOnInit() looks like:
ngOnInit(): void {
this.retrieveBooks();
this.retrieveReviews();
this.setRatingToTen();
}
With this I write Books, and Reviews to object arrays.
This is done through a "subscription" to data through services:
retrieveBooks(): void {
this.bookService.getAll()
.subscribe(
data => {
this.books = data;
},
error => {
console.log(error);
}
);
}
retrieveReviews(): void {
this.reviewService.getAll()
.subscribe(
data => {
this.reviews = data;
},
error => {
console.log(error);
});
}
So this next function I have is just an example of "working with the data".
In this example, I just want to change all of the totalratings to 10 for each Book:
setRatingToTen(): void {
this.books.forEach(element => {
element.totalrating = 10;
});
console.log(this.books);
}
The problem I have been trying to wrap my head around is this:
this.books is an empty array.
I THINK the reason is because this function is running before the data subscription.
IF this is the case, then my understanding of ngOnInit must not be right.
I thought it would call the function in order.
Maybe that's still the case, it's just that they don't complete in order.
So my questions are:
1. Why is it an empty array?
(was I right? or is there more to it?)
2. How do Angular developers write functions so they operate in a desired order?
(since the data needs to be there so I can work with it, how do I avoid this issue?)
(3.) BONUS question:
(if you have the time, please and thank you)
My goal is to pull this.reviews.rating for each book where this.reviews.title equals this.books.title, get an average score; and then overwrite the "0" placeholder of this.books.totalrating with the average. How could I re-write the setRatingToTen() function to accomplish this?
Here is one of solution using forkJoin method in rxjs .
you can check this for details https://medium.com/#swarnakishore/performing-multiple-http-requests-in-angular-4-5-with-forkjoin-74f3ac166d61
Working demo : enter link description here
ngOnInit:
ngOnInit(){
this.requestDataFromMultipleSources().subscribe(resList => {
this.books = resList[0];
this.reviews = resList[1];
this.setRatingToTen(this.books,this.reviews);
})
}
forkJoin method:
public requestDataFromMultipleSources(): Observable<any[]> {
let response1 = this.retrieveBooks();
let response2 = this.retrieveReviews();
// Observable.forkJoin (RxJS 5) changes to just forkJoin() in RxJS 6
return forkJoin([response1, response2]);
}
Other methods:
retrieveBooks(): void {
this.bookService.getAll()
.subscribe(
data => {
this.books = data;
},
error => {
console.log(error);
}
);
}
retrieveReviews(): void {
this.reviewService.getAll()
.subscribe(
data => {
this.reviews = data;
},
error => {
console.log(error);
});
}
setRatingToTen(books, reviews): void {
this.books.forEach(element => {
element.totalrating = 10;
});
console.log(this.books);
}
Angular makes heavy use of observables to handle variety of asynchronous operations. Making server side requests (through HTTP) is one of those.
Your first two questions clearly reflect you are ignoring the asynchronous nature of observables.
Observables are lazy Push collections of multiple values. detailed link
Means observable response would be pushed over time in an asynchronous way. You can not guarantee which of the two distinct functions would return its response first.
Having said that, rxjs library (Observables are also part of this library and angular borrowed them from here) provides a rich collection of operators that you can use to manipulate observables.
With the above explanation, here is one by one answer to your questions.
1. Why is it an empty array?
Because you are thinking in terms of synchronous sequential flow of code, where one method would get called only after the other has finished with its working. But here retrieveBooks and retrieveReviews both are making asynchronous (observable) calls and then subscribing to it. This means there is no guarantee when their response would be received. Meanwhile the hit to setRatingToTen had already been made, at that point in time books array was empty.
2. How do Angular developers write functions so they operate in a desired order?
Angular developer would understand the nature of asynchronous observable calls, and would pipe the operators in an order so that they are sure they have the response in hand before performing any further operation on the observable stream.
(3.) BONUS question:
Your requirement specifies that you must first have the response of both observables at hand before performing any action. For that forkJoin rxjs operator suits your need. Documentation for this operator say
One common use case for this is if you wish to issue multiple requests on page load (or some other event) and only want to take action when a response has been received for all. detailed link
Not sure about your average score strategy, but here is an example code how you would achieve your purpose.
ngOnInit(){
let req1$ = this.bookService.getAll();
let req2$ = this.reviewService.getAll();
forkJoin(req1$, req2$).subscribe(([response1, response2])=>{
for(let book of response1) //loop through book array first
{
for(let review of response2) //inner loop of reviews
{
if(book.title == review.title)
{
//complete your logic here..
}
}
}
});
}
Related
Im refactoring a billing system made with Angular + NodeJs.
I need to make some API calls to my backend, first to get Customers, then Invoices and finally Students. I call the functions in ngOnInit. Then I need to manipulate the Arrays mixing the data.
If I call the functions in order, sometimes the code can't access all the info, for example:
ngOnInit(): void {
this.getCustomers();
this.getInvoices();
this.getStudents();
this.doSomeCalculations();
Sometimes customers[] (defined by this.getCustomers() is still empty when this.doSomeCalculations() starts.
I tried with setTimeout, with no improvement.
Finally what I did to make everything work was calling getInvoices() in the getCustomers() response, and getStudents() in getInvoices() response. So getInvoices() asign the response to invoices[] and then calls the next function, and so on. And doSomeCalculations() only starts when all the data has been asigned to variables.
But this solution is ugly. I know there is a better way of doing this, but I don't know how. Maybe it is related to promises or async-await?
My base code for the API calls is as follows:
getCustomers(){
this._customerService.getCustomers()
.subscribe(
{ next: (res) => {
this.customers = res;
this.getInvoices();
},
error: (err) => {
console.log(<any>err);
}
}
);
}
It is the same for getInvoices and getStudents.
How can I improve my code here?
You could try using async/await + promises.
For each method definition, you could return the data as a promise, then use async/await at the top level. I see you're using observables for the data calls, but you don't have to. Promises may be better here.
Example:
async ngOnInit(): Promise<void> {
try {
await getCustomers();
...
} ...
}
getCustomers(): Promise<Customers[]>{
return new Promise((err, resp) => {
...
resolve(resp)
})
}
Given this method:
public logIn(data:any): Observable<any> {
this.http.get('https://api.myapp.com/csrf-cookie').subscribe(() => {
return this.http.post('https://api.myapp.com/login', data);
});
}
I would like it to return that nested observable, so that my calling code can use it like so:
this.apiService.logIn(credentials).subscribe(() => {
// redirect user to their dashboard
});
without needing to know about the first /csrf-cookie request. Obviously the above doesn't work - but I'm struggling to understand how to make the inner HTTP request wait for the outer one to finish AND be returned by the method.
you should use switchMap see the documentation on switch map
public logIn(data:any): Observable<any> {
return this.http.get('https://api.myapp.com/csrf-cookie').pipe(
switchMap(x => this.http.post('https://api.myapp.com/login', data))
);
}
with rxjs nested subscribes are generally not a good idea. There are many great operators within the library that will get you around it. In this case above where one call depends on another switchMap(...) is the best fit.
Also the code has been modified to return the observable not the subscription
I've just finished my Angular lessons and I already find out some differences between what I learn and the Angular official documentation.
Let's imagine I want to recover an user with ID of an API.
Here is how I would do it according to my lessons :
export class UserService {
constructor(
private httpClient: HttpClient
) {
}
public user: User; // local variable using User model
public userSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
async getSingleUserFromServer() {
await this.httpClient.get<any>('https://xvalor.repliqa.fr/api/v1/user/' + this.userId).subscribe(
(response) => {
this.user = response;
this.userPortfolios = this.user.portfolioAssoc;
this.emitSubjects();
});
}
emitSubjects() {
this.userSubject.next(this.user);
}
}
and here is how angular doc procceed
getHeroes (): Observable<Hero[]> {
return this.http.get<Hero[]>(this.heroesUrl)
.pipe(
tap(_ => this.log('fetched heroes')),
catchError(this.handleError<Hero[]>('getHeroes', []))
);
}
I understand than both methods are quiet doing the same thing, I just want to be sure which one I should use, especially in big project developpement.
I would stick to the second approach as it is more generic and it uses Observable. Observale allow to emit any count of events and callback will be called for each event. Promise generates a single event after completion.
In addition, service class should not have async and await parts. The goal of service is to return data and UI component can consume data using async and await parts. async and await are syntactic sugar to avoid writing .subscribe part as it is really verbose. So write async and await in your UI components.
If you want to use Promise, then your service should not have subscribe part:
getSingleUserFromServer() {
return this.httpClient.get<any>('https://xvalor.repliqa.fr/api/v1/user/' + this.userId);
}
However, it is better to return Observables from your service.
Your first approach is flawed in that the consumer must perform two separate operations: call getSingleUserFromServer() to make the call, and subscribe to UserService.user to consume the results. And in case of errors, he won't receive any feedback.
Stick to the official guidelines for now. BTW, if your goal was to additionally store the user as a variable available to everyone, then with the Observable pattern it's as simple as adding another tap to the pipe:
httpClient.get(url)
.pipe(
someOperator(),
tap(user => this.user = user),
anotherOperator(...someArgs),
)
Observables and Subjects are two diffrent objects from rxjs and bring diffrent properties with them. The answers to this question show some of the key differences: What is the difference between a Observable and a Subject in rxjs?
I am currently struggling to wrap my head around angular (2+), the HttpClient and Observables.
I come from a promise async/await background, and what I would like to achieve in angular, is the equivalent of:
//(...) Some boilerplate to showcase how to avoid callback hell with promises and async/await
async function getDataFromRemoteServer() {
this.result = await httpGet(`/api/point/id`);
this.dependentKey = someComplexSyncTransformation(this.result);
this.dependentResult = await httpGet(`/api/point/id/dependent/keys/${this.dependentKey}`);
this.deeplyNestedResult = await httpGet(`/api/point/id/dependen/keys/${this.dependentResult.someValue}`);
}
The best I could come op with in angular is:
import { HttpClient } from `#angular/common/http`;
//(...) boilerplate to set component up.
constructor(private http: HttpClient) {}
// somewhere in a component.
getDataFromRemoteServer() {
this.http.get(`/api/point/id`).subscribe( result => {
this.result = result;
this.dependentKey = someComplexSyncTransformation(this.result);
this.http.get(`/api/point/id/dependent/keys/${this.dependentKey}`).subscribe( dependentResult => {
this.dependentResult = dependentResult;
this.http.get(`/api/point/id/dependen/keys/${this.dependentResult.someValue}`).subscribe( deeplyNestedResult => {
this.deeplyNestedResult = deeplyNestedResult;
});
})
});
}
//...
As you might have noticed, I am entering the Pyramid of Doom with this approach, which I would like to avoid.
So how could I write the angular snippet in a way as to avoid this?
Thx!
Ps: I am aware of the fact that you can call .toPromise on the result of the .get call.
But let's just assume I want to go the total Observable way, for now.
When working with observables, you won't call subscribe very often. Instead, you'll use the various operators to combine observables together, forming a pipeline of operations.
To take the output of one observable and turn it into another, the basic operator is map. This is similar to how you can .map an array to produce another array. For a simple example, here's doubling all the values of an observable:
const myObservable = of(1, 2, 3).pipe(
map(val => val * 2)
);
// myObservable is an observable which will emit 2, 4, 6
Mapping is also what you do to take an observable for one http request, and then make another http request. However, we will need one additional piece, so the following code is not quite right:
const myObservable = http.get('someUrl').pipe(
map(result => http.get('someOtherUrl?id=' + result.id)
)
The problem with this code is that it creates an observable that spits out other observables. A 2-dimensional observable if you like. We need to flatten this down so that we have an observable that spits out the results of the second http.get. There are a few different ways to do the flattening, depending on what order we want the results to be in if multiple observables are emitting multiple values. This is not much of an issue in your case since each of these http observables will only emit one item. But for reference, here are the options:
mergeMap will let all the observables run in whatever order, and outputs in whatever order the values arrive. This has its uses, but can also result in race conditions
switchMap will switch to the latest observable, and cancel old ones that may be in progress. This can eliminate race conditions and ensure you have only the latest data.
concatMap will finish the entirety of the first observable before moving on to the second. This can also eliminate race conditions, but won't cancel old work.
Like i said, it doesn't matter much in your case, but i'd recommend using switchMap. So my little example above would become:
const myObservable = http.get('someUrl').pipe(
switchMap(result => http.get('someOtherUrl?id=' + result.id)
)
Now here's how i can use those tools with your code. In this code example, i'm not saving all the this.result, this.dependentKey, etc:
getDataFromRemoteServer() {
return this.http.get(`/api/point/id`).pipe(
map(result => someComplexSyncTransformation(result)),
switchMap(dependentKey => this.http.get(`/api/point/id/dependent/keys/${dependentKey}`)),
switchMap(dependantResult => this.http.get(`/api/point/id/dependent/keys/${dependentResult.someValue}`)
});
}
// to be used like:
getDataFromRemoteServer()
.subscribe(deeplyNestedResult => {
// do whatever with deeplyNestedResult
});
If its important to you to save those values, then i'd recommend using the tap operator to highlight the fact that you're generating side effects. tap will run some code whenever the observable emits a value, but will not mess with the value:
getDataFromRemoteServer() {
return this.http.get(`/api/point/id`).pipe(
tap(result => this.result = result),
map(result => someComplexSyncTransformation(result)),
tap(dependentKey => this.dependentKey = dependentKey),
// ... etc
});
}
I might be off on the process, but here goes:
I have an angular2 service. The source for the data of this service is going to be localstorage... later optionally updated when a DB call using http returns. Because I'll be wanting to update the data returned as the various sources come back, it appears I want to use an observables. For now, I'm just trying to get the concept down, so I've skipped the localstorage aspect... but I'm including the 'backstory' so it makes (some) sense as to why I'm wanting to do this in multiple methods.
My thought was I would have a "getHTTPEvents()" method that would return an observable with the payload being the events from the DB. (the theory being that at some point in the future I'd also have a 'getLSEvents()' method that would piggy back in there)
To mock that up, I have this code:
private eventsUrl = 'app/mock-events.json';
getHTTPEvents() : Observable<Array<any>> {
return this._http.get(this.eventsUrl)
.map(response => response.json()['events'])
.catch(this.handleError); // handle error is a logging method
}
My goal would be to create a method that allows filtering on the returned events yet still returns an observable to users of the service. That is where my problem is. With that goal, I have a public method which will be called by users of the service. (attempted to use pattern from here https://coryrylan.com/blog/angular-2-observable-data-services)
public getEvents(key:string,value:string) : Observable<Array<any>> {
var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();
var filteredEventsObserve : Observable<Array<any>>;
allEventsObserve
.subscribe(
events => {
for(var i=0;i<events.length;i++) {
if(events[i][key]==value) {
console.log('MATCH!!!' + events[i][key]); // THIS WORKS!
return new Observable(observer => filteredEventsObserve = observer); // what do I need to return here? I want to return an observable so the service consumer can get updates
}
}
return allEventsObserve
},
error => console.error("Error retrieving all events for filtering: " + error));
}
The above doesn't work. I've watch lots of videos and read lots of tutorials about observables, but nothing I can find seems to go more indepth other than creating and using the http observable.
I further tried this method of making the new observable:
var newObs = Observable.create(function (observer) {
observer.next(events[i]);
observer.complete(events[i]);
});
And while at least that compiles, I'm not sure how to 'return' it at the right time... as I can't "Create" it outside the allEventsObserve.subscribe method (because 'events' doesn't exist) and can't (seem) to "return" it from within the subscribe. I'm also not entirely sure how I'd then "trigger" the 'next'...?
Do I need to modify the data within allEventsObserve and somehow simply still return that? Do I make a new observable (as attempted above) with the right payload - and if so, how do I trigger it? etc... I've checked here: How to declare an observable on angular2 but can't seem to follow how the 'second' observable gets triggered. Perhaps I have the entire paradigm wrong?
It appears that you're misunderstanding what an RxJS operator (like map, filter, etc) actually returns, and I think correcting that will make the solution clear.
Consider this short example:
allEventsObserve
.map(events => {
return 'this was an event';
})
Granted, it's a pretty useless example since all of the data from events is lost, but let's ignore that for now. The result of the code above is not an array of strings or anything else, it's actually another Observable. This Observable will just emit the string 'this was an event' for each array of events emitted by allEventsObserve This is what allows us to chain operators on observables -- each operator in the chain returns a new Observable that emits items that have been modified in some way be the previous operator.
allEventsObserve
.map(events => {
return 'this was an event';
})
.filter(events => typeof events !== 'undefined')
allEventsObserve is obviously an Observable, allEventsObserve.map() evaluates to an Observable, and so does allEventsObserve.map().filter().
So, since you're expecting your function to return an Observable, you don't want to call subscribe just yet, as doing so would return something that isn't really an Observable.
With that in mind, your code can be rewritten in the following way:
public getEvents(key:string,value:string) : Observable<Array<any>> {
var allEventsObserve : Observable<Array<any>> = this.getHTTPEvents();
return allEventsObserve
.map(events => {
var match = events.filter(event => event[key] == value);
if (match.length == 0) {
throw 'no matching event found';
} else {
return match[0];
}
})
.catch(e => {
console.log(e);
return e;
});
}
Since getEvents returns an Observable, somewhere else in your code you would do something like getEvents().subscribe(events => processEvents()) to interact with them. This code also assumes that this.getHTTPEvents() returns an Observable.
Also, notice that I changed your for loop to a call to filter, which operates on arrays. events in this case is a plain-old JavaScript Array, so the filter that is getting called is not the same filter as the RxJS operator filter.