Having problem with using forEach function with http requests.
I have a _watchlistElements variable, which holds following data:
[{"xid":"DP_049908","name":"t10"},{"xid":"DP_928829","name":"t13"},{"xid":"DP_588690","name":"t14"},{"xid":"DP_891890","name":"t16"},{"xid":"DP_693259","name":"t17"}]
Now, Im making a function which will download data from server for each of these xid elements:
private download() {
this._watchlistElements.forEach(v =>
this.http.get('http://localhost:8080/getValue/' + v.xid)
.subscribe(res => this._values = res.json()));
}
It has to download data as object for every v.xid value and store it inside the _values variable.
private _values: Array<WatchlistComponent> = [];
But somehow, angular returns an error with v.xid element. It doesn't see that variable. But it's kinda strange, because when I do it just in console, I mean: store that json inside a variable and use forEach function on this v.xid elements, everything works well.
ERROR in [default] C:\Users\src\app\appBody\watchlist\watchl
ist.component.ts:51:115
Property 'xid' does not exist on type 'WatchlistComponent'.
The xid exists... but inside the _watchlistElements which downloads the data asynchonously...
I'm not 100% sure this method is right, but if you have any ideas how to fix it, please tell me.
What happens when you print out the _values array?
The error above is a type error. What does the WatchlistComponent interface look like? Does it include an xid property?
You can get around the type error by overriding the type like...
private download() {
this._watchlistElements.forEach((v as any) =>
this.http.get('http://localhost:8080/getValue/' + v.xid)
.subscribe(res => this._values = res.json()));
}
As far as helping you structure your code better. If you want to combine the result of many Observables, I would use something like forkJoin.
private download():void {
//create an array of Observables
let el$ = _watchlistElements.map(el => {
return this.http.get('http://localhost:8080/getValue/' + el.xid)
.map(res: Response => <any>res.json());
});
//subscribe to all, and store the data, el$ is an array of Observables
Observable.forkJoin(el$).subscribe( data => {
this._values = data; //data will be structured as [res[0], res[1], ...]
});
}
HERE is a Plunker with the above method working. https://plnkr.co/edit/woXUIDa0gc55WJPOZMKh?p=preview
Related: angular2 rxjs observable forkjoin
Related
I'm performing multiple task and each task is dependent on previous task execution. So in my example what I want is after getting all the Id, i should get their respective blob value and then finish the execution by storing it in a variable. I'm very new to javascript and angular, please help me out. Here's what I'm trying
//this method will get the response from the rest api
async getIDFromAssets(){
this.blobDataArray=[];
this.service.getAssetsData().subscribe(async (res: JSON) => {
//after getting the response I'm filtering through it to get sepcific Id using this.getFileId() method
this.getFileId(res).then((data)=>{
console.log("blob "+data)
})
})
}
//below method will get one Id at a time and will call another method to get it's blob value
async getFileId(res){
this.fileId = [];
Object.keys(res).forEach(keys => {
if (keys == 'emb') {
let responseValue = res[keys];
Object.keys(responseValue).forEach(async (keys1) => {
if (keys1 === 'file') {
let responseArray = responseValue[keys1];
for (let file of responseArray) {
let temp: string = file.metadata.contentType;
if (temp.startsWith('image')) {
//Here I'm getting id value 'file._id' and using that I'm calling another method 'getBlobData()' to get its blob value
let data=await this.getBlobData(file._id);
this.blobDataArray.push(data);
}
}
return this.blobDataArray
}
});
}
});
}
// method to get the blob value
async getBlobData(fileId){
this.articleDetailService.getBlobDataFromAssets(fileId).subscribe(async (res)=>{
let imageObj={
'id':fileId,
'blob':res
}
return imageObj;
})
}
You need to use RxJs to avoid the nested subscription to chain your calls, possible methods to use are mergeMap and filter
Please take a look at this answer here.
In a parent component I have a stream of Tour[] tours_filtered: Observable<Tour[]> which I assign in the subscribe function of an http request
this.api.getTours().subscribe(
result => {
this.tours_filtered = of(result.tours);
}
)
in the view I display the stream using the async pipe
<app-tour-box [tour]="tour" *ngFor="let tour of tours_filtered | async"></app-tour-box>
Up to here all works as expected. In a child component I have an input text which emits the value inserted by the user to filtering the array of Tour by title.
In the parent component I listen for the emitted values in a function, I switch to new stream of Tour[] filtered by that value using switchMap
onSearchTitle(term: string) {
this.tours_filtered.pipe(
switchMap(
(tours) => of( tours.filter((tour) => tour.name.toLowerCase().includes(term)) )
)
)
}
I thought that the async pipe was constantly listening to reflect the changes to the array to which it was applied and so I thought I didn't have to subscribe in the function above, but nothing change in the view when I type in the input to filtering the results.
The results are updating correctly if I assign the new stream to the original array in the subscribe function
onSearchTitle(term: string) {
this.tours_filtered.pipe(
switchMap((tours) => of(tours.filter((tour) => tour.name.toLowerCase().includes(term))))
).subscribe( val => { this.tours_filtered = of(val); })
}
Is this procedure correct? Could I avoid to subscribe because I already use the async pipe? There is a better way to reach my goal?
EDITED:
Maybe I found a solution, I have to reassing a new stream to the variable just like this
onSearchTitle(term: string) {
this.tours_filtered = of(this.city.tours).pipe(
switchMap((tours) => of(tours.filter((tour) => tour.name.toLowerCase().includes(term))))
);
}
and I don't need to subscribe again, the results in the view change according to the search term typed by the user. Is this the correct way?
I think in your situation the solution should work as follows:
onSearchTitle(term: string) {
this._searchTerm = term;
this.tours_filtered = of(
this.city.tours.filter((tour) => tour.name.toLowerCase().includes(term))
)
}
Because in your example you don't change the observable which is used in ngFor. Thus it's not working.
However, I don't see the reason of using observables here unless this is the first step and you're going to fetch this data from server in future
UPDATE
The best solution for you would be to consider your input as an observable and watch for the changes:
// your.component.ts
export class AppComponent {
searchTerm$ = new BehaviorSubject<string>('');
results = this.search(this.searchTerm$);
search(terms: Observable<string>) {
return terms
.pipe(
debounceTime(400),
distinctUntilChanged(),
switchMap(term => {
return of(this.city.tours.filter((tour) => tour.name.toLowerCase().includes(term)))
}
)
)
}
}
// your.template.html
...
<input type="" (input)="searchTerm$.next($event.target.value)">
...
Additionally it would be great to add debounceTime and distinctUntilChanged for better user experience and less search requests.
See full example for the details. Also please, refer to this article for more detailed explanations
I am subscribing to an observable to get the response from an external API. Then I perform another filtering function on it. But when I do so, it also filters the original response. I want to preserve the original response and also get the new filtered response. When I subscribe without implementing the getFilteredEntryByProv function, I get the full response in my 'Header Entries' console log. Once I had the filter function, the 'Header Entries; console log also gets updated.....Any suggestions?
this.subscription = this.contentfulService.getContentfulEntry(this.headerEntryId, {locale: this.lang.toLowerCase()})
.subscribe(res => {
console.log('Header Entries:: ', res);
this.filteredHeader = this.contentfulService.getFilteredEntryByProv(res, this.prov);
console.log('Filtered Header:: ', this.filteredHeader);
In getFilteredEntryByProv you are modifying the response itself. provObj.data = _.filter modifies the res.fields.data.
You should just return filtered data from your function and leave original request as it is:
public getFilteredEntryByProv = (res, prov: string) => {
return _.filter(res.fields.data, obj => _.includes(obj.prov, prov));
}
Every time I load the webpage, I'd have to click the logo in-order my data to fully populate the local array in my component. The data fetched is located in a local JSON file. Having to refresh the page every-single-time is fairly unprofessional/annoying.
Using Angular CLI 1.3.2
Here's where my problem lies:
#Injectable()
export class LinksService implements OnInit{
siteFile : IFile[];
constructor(private http: Http) {
this.getJSON().subscribe(data => this.siteFile = data, error =>
console.log(error));
}
public getJSON(): Observable<any> {
return this.http.get('./assets/docs/links.json')
.map((res:any) => res.json());
}
getAllIpageLinks() : IPageLink[]{
var selectedIPageLinks: IPageLink[] = new Array();
var selectedFileLinks : IFile[] = new Array();
selectedFileLinks = this.siteFile;
for (var i=0; i<selectedFileLinks.length; i++)
{
selectedIPageLinks =
selectedIPageLinks.concat(selectedFileLinks[i].files);
}
return selectedIPageLinks.sort(this.sortLinks);
}
Component:
constructor(private elRef: ElementRef, private linksService: LinksService) {
this._file = this.linksService.getAllIpageLinks();
}
Edit
The title has to be clicked in order for array of IFile[] to completely render. I've tried setting IFile to an empty array (IFile[] = []) The error goes away, however, it will render empty data.
The problem seems to be in the For loop, it can't recognize .length.
Problem :
The codes are correct but the approach is wrong. Subscribing to an Observable getJSON() is async task. Before any data is being returned by getJSON(), you already calls getAllIpageLinks() and therefore you get null value on very first run. I believe since you have injected the service as singleton in component, the data gets populated in subsequent call( on refresh by clicking logo).
Solution:
Apply the changes (that you are making in getAllIpageLinks ) by using map operator on observable.
return the instance of that observable in the component.
subscribe to that observable in the component(not in .service)
Welcome to StackOverflow. Please copy paste your codes in the question instead of giving screenshot of it. I would be able than to give you along the exact codes
Reference Codes :
I haven't tested the syntax but should be enough to guide you.
1. Refactor getAllIpageLinks() as below
public getAllIpageLinks(): Observable<any> {
return this.http.get('./assets/docs/links.json')
.map((res:any) => res.json());
.map(res => {
var selectedIPageLinks: IPageLink[] = new Array();
var selectedFileLinks : IFile[] = new Array();
selectedFileLinks = res;
for (var i=0; i<selectedFileLinks.length; i++)
{
selectedIPageLinks =
selectedIPageLinks.concat(selectedFileLinks[i].files);
}
return selectedIPageLinks.sort(this.sortLinks);
});
}
call above getAllIpageLinks() in your component
and subscribe to it there
I am using angualr 4.
i have an array.
Here is my array;
this.bookings = [
{
'id':'dsjdsfhkdsjhfjkds01'
},
{
'id':'dsjdsfhkdsjhfjkds01'
}
]
I need retrieve data from database based on id.
Here is my script.
let scope = this;
scope.bookings.forEach(function(BookingItem){
var bId = BookingItem.id;
console.log("BId",bId);
scope.Bservice.getbooking(scope.at,bId).subscribe(booking => {
var responseVal = booking;
})
})
I need like forEach Take on firstvalue then get retrive data from database.After going to second value of booking then get data from database.
But i consoled value of bId.
ForEach taken on id values one by one After retreive data from database.
How can i fix this pblm.
Kindly advice me,
Thanks.
I am not understading you code at full but you have to do like this
You have to loop throught bookings array and than in argument of foreach you need vriable name not name of you class ,
Another thing is if you go in loop, you last returned value from ajax request will override vlaue of your variable , so better to store response in array thats why below code use array to store you response for each id.
let responseFromServer = new Array<any>();
this.bookings.forEach((bookingItem) => {
var bId=bookingItem.id;
console.log("BId",bId);
scope.Bservice.getbooking(scope.at,bId).subscribe(booking=>{
let response = new ResponseFromServer();
responseFromServer.Add(booking);
});
});
Since the api call response takes some time, you should wait for the response untill it is recieved.
You can use Observable forkJoin which works like a promise and processes all requests in loop and returns response.
import {Observable} from 'rxjs/Rx';
let observables = new Array();
this.bookings.forEach(function(booking){
observables.push(this.Bservice.getbooking(this.at,booking.id));
})
Observable.forkJoin(observables).subscribe(
res => console.log(res),
error => console.log('Error: ', error)
);
Here is a documentation for forkJoin