Http subscribe data not assigning to variable - javascript

I want to fetch data from api and assign it to my variable.
In subscribe I assigned data to variable in subscribe and console logged variable. Everything okay, variable has now data, but after subscribe ends busDepotLocality will still be undefined. Tried to put timeout to await, but nothing
app.component.ts:
busDepotLocality: BusDepotLocality;
ngOnInit() {
this.mapService
.getBusDepotLocality()
.subscribe((data: BusDepotLocality) => {
this.busDepotLocality = data;
console.log(data); // Data is showing, no problem
console.log(this.busDepotLocality); //Assignment passed, data is showing, no problem
});
console.log(this.busDepotLocality); // But after all busDepotLocality will be undefined

try this :
async ngOnInit() { // Step 1
await this.mapService // Step 2
.getBusDepotLocality()
.toPromise().then((data: BusDepotLocality) => {
this.busDepotLocality = data; // Step 3
console.log(data); // Data is showing, no problem
console.log(this.busDepotLocality); //Assignment passed, data is showing, no problem
});
console.log(this.busDepotLocality); // Step 4
}
To Clarify on how we can have async-await in lifecycle hook, here are the steps :
1) In Step 1 we are making the hook async, thus it will not hamper/impact this lifecycle hook as we are not awaiting for this method call at step 1, and it will continue executing other cycle events
2) In Step 2, we are awaiting till we get the response and any lines of code following await, will wait till we get the response of async call ( "http" call )
3) In Step 3, we get the response.
4) In Step 4, we execute rest of the statements.
PS : Since here we are asking to execute statements once everything is complete and you get final response, you need to convert it to promise.
And, if you insist on considering observable you can use async pipe in html
\\ Component
private data:Observable<any>;
ngOnInit(){
this.data=this.mapService.getBusDepotLocality();
}
\\ HTML
{{data|async}}
Else, you can pipe(tap()) your response

You are trying to log something that is not populated yet.
as proof of this, try
busDepotLocality: BusDepotLocality;
ngOnInit() {
this.mapService
.getBusDepotLocality()
.subscribe((data: BusDepotLocality) => {
this.busDepotLocality = data;
console.log(data); // Data is showing, no problem
console.log(this.busDepotLocality); //Assignment passed, data is showing, no problem
},(error) => {console.log(error)},() => {console.log(this.busDepotLocality); });

In order to understand the problem you are having, you need to have a better understanding of subscriptions. In the easiest way I can think of right now, think about a subscription like it is a live stream, which means as long as you are steaming the content you will have access to the data. Now let's apply this concept to the problem you are having, we have the following code:
busDepotLocality: BusDepotLocality;
ngOnInit() {
this.mapService
.getBusDepotLocality()
.subscribe((data: BusDepotLocality) => {
this.busDepotLocality = data;
console.log(data); // Data is showing, no problem
console.log(this.busDepotLocality); //Assignment passed, data is showing, no problem
});
console.log(this.busDepotLocality); // But after all busDepotLocality will be undefined
You actually indicate my point with your comment - within the subscription the data is accessible, outside of the subscription, the data cannot be assigned. You would want to map the data then subscribe to the variable whenever you want to access the data. for example:
busDepotLocality: BusDepotLocality;
ngOnInit() {
this.busDepotLocality = this.mapService
.getBusDepotLocality()
.pipe(map((data) => data));
const results = this.busDepotLocality.subscribe(val =>
console.log(val)
//code goes here
);
}

Related

Angular subscribes not working how I expect

I'm at a loose end here and trying to understand the flow of how angular subscriptions work.
I make a call to an API and in the response I set the data in a behaviourSubject. So I can then subscribe to that data in my application.
Normally I would use async pipes in my templates cause its cleaner and it gets rid of all the subscription data for me.
All methods are apart of the same class method.
my first try.....
exportedData: BehaviourSubject = new BehaviourSubject([]);
exportApiCall(id) {
this.loadingSubject.next(true)
this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught)),
finalize(() => => this.loadingSubject.next(false))
).subscribe(res => {
this.exportedData.next(res)
})
}
export(collection) {
let x = []
this.exportCollection(collection.id); /// calls api
this.exportedData.subscribe(exportData => {
if(exportData){
x = exportData
}
})
}
console.log(x)//// first time it's empthy, then it's populated with the last click of data
/// in the template
<button (click)="export(data)">Export</button>
My problem is....
There is a list of buttons with different ID's. Each ID goes to the API and gives back certain Data. When I click, the console log firstly gives a blank array. Then there after I get the previous(the one I originally clicked) set of data.
I'm obviously not understanding subscriptions, pipes and behavior Subjects correctly. I understand Im getting a blank array because I'm setting the behaviour subject as a blank array.
my other try
export(collection) {
let x = []
this.exportCollection(collection.id).pip(tap(res => x = res)).subscribe()
console.log(x) //// get blank array
}
exportApiCall(id) {
return this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught))
)
}
Not sure about the first example - the placement of console.log() and what does the method (that is assigned on button click) do - but for the second example, you're getting an empty array because your observable has a delay and TypeScript doesn't wait for its execution to be completed.
You will most likely see that you will always receive your previous result in your console.log() (after updating response from API).
To get the initial results, you can update to such:
public exportReport(collection): void {
this.exportCollection(collection.id).pipe(take(1)).subscribe(res => {
const x: any = res;
console.log(x);
});
}
This will print your current iteration/values. You also forgot to end listening for subscription (either by unsubscribing or performing operators such as take()). Without ending listening, you might get unexpected results later on or the application could be heavily loaded.
Make sure the following step.
better to add console.log inside your functions and check whether values are coming or not.
Open your chrome browser network tab and see service endpoint is get hitting or not.
check any response coming from endpoints.
if it is still not identifiable then use below one to check whether you are getting a response or not
public exportReport(collection): void {
this.http.get(url+"/"+collection.id).subscribe(res=> {console.log(res)});
}
You would use BehaviourSubject, if there needs to be an initial/default value. If not, you can replace it by a Subject. This is why the initial value is empty array as BehaviourSubject gets called once by default. But if you use subject, it wont get called before the api call and you wont get the initial empty array.
exportedData: BehaviourSubject = new BehaviourSubject([]);
Also, you might not need to subscribe here, instead directly return it and by doing so you could avoid using the above subject.
exportApiCall(id) {
this.loadingSubject.next(true);
return this.api.getReport(id).pipe(
catchError((err, caught) => this.errorHandler.errorHandler(err, caught)),
finalize(() => => this.loadingSubject.next(false))
);
}
Console.log(x) needs to be inside the subscription, as subscribe is asynchronous and we dont knw when it might get complete. And since you need this data, you might want to declare in global score.
export(collection) {
// call api
this.exportApiCall(collection.id).subscribe(exportData => {
if (exportData) {
this.x = exportData; // or maybe this.x.push(exportData) ?
console.log(this.x);
}
});
}

use values from observable in angular when they are ready

I'm getting data from observable then manipulating it.
My actual problem is that when I would use data from observable after calling it never get arrived.
But when I console.log result inside subscribe() method at a moment data comes.
I thought that Angular will call again lifecyclehooks when I get data but it's not the case.
refreshData() {
this.apiService.retrieveIndicatorHistory(this.indicatorName, this.currentPeriod).subscribe(res => {
this.values$.push(res);
console.log("values",res)
});
}
ngOnInit() {
this.refreshData();
... few steps after
console.log("values are here", this.values$); // always empty
}
Then I tried to show values inside Onchange also but data never prints
ngOnChanges() {
console.log("values are here", this.values$);
// manipulate data
}
How can I manipulate values that I get from observable when they get ready ?
Should I put all my business logic inside subscribe() ?
RxJS Observable works in async manner, it will emit the data once after it is received. So input and output will be an observable. In order to read the observable data, first you need to subscribe to it.
refreshData() {
this.apiService.retrieveIndicatorHistory(this.indicatorName, this.currentPeriod).subscribe(res => {
this.values$.push(res);
this.processData();
});
}
ngOnInit() {
this.refreshData();
}
processData(){
console.log(this.values$); // you can access data here.
}
You should not put business logic in subscribe. In general, all business logic should be placed in pipe(). And you can subscribe to observable directly in hTML using async pipe.
So in html should be:
<ul *ngFor="let value of (values$ | async)">
<li>{{value}}</li>
</ul>
and inside component.ts:
values = []
values$ = this.apiService.retrieveIndicatorHistory(this.indicatorName, this.currentPeriod).pipe(
map(res => {
this.values.push(res);
return this.values
})
)
Yes, It's an async call and the subscribe will exactly know in time when data arrives, anything in ngOnInit will execute and won't wait for it.
Else you can use (async, await ) combination

assigning value to a variable in angular component from a subscribe of observeble HTTP request [duplicate]

This question already has answers here:
How do I return the response from an Observable/http/async call in angular?
(10 answers)
Closed 4 years ago.
when I want to assign a value to a local variable in Angular component from subscribing HTTP request
I get undefined
patient: Patient;
hpId: any;
ngOnInit() {
const id = +this.route.snapshot.paramMap.get('id');
this.fetchPatient(id);
console.log(this.hpId);
}
fetchPatient(id: number) {
this.patientService.getPatient(id)
.subscribe( data => {
this.patient = data;
this.hpId = this.patient.appointment[0].healthProfessionalId;
});
}
}
I want to use the value of hpId out of the scope of subscribing method
There are two ways:
1) If you want to display hdId in HTML page then use like {{ hpId }}
2) If you want to call another API then you can wrap that call inside .subscribe() call like
fetchPatient(id: number) {
this.patientService.getPatient(id)
.subscribe( data => {
this.patient = data;
this.hpId = this.patient.appointment[0].healthProfessionalId;
this.yourMethod(this.hdId); // here
});
}
yourMethod(hdId){
console.log(hdId); // do whatever with `hdId`
}
Coding in typescript is like plumbing..you have to code like a
stream. If you turn the tap on but if the water is on the way until it reaches the
tap there won't be water so if you planned to wash your clothes you have to do it
after the water comes from the tap.
So when you call the method this.fetchPatient(id); it will hit the
back-end and wait in the subscribe until it gets data or error.after
the this.fetchPatient(id); then you put your console.log(this.hpId);
at this point data is not retrieved but the call was made therefore
it will show undefined. if you want to see data put the console log
inside subscribe after this line this.hpId =
this.patient.appointment[0].healthProfessionalId;
Furthermore if you want to use value of this.hpId in other methods
call those methods after this.hpId =
this.patient.appointment[0].healthProfessionalId; line.

switchMap operation only running on first call?

I have an angular application that makes a request to an Http service and calls a switchMap on another Http service. For some reason the request in the switchMap only runs the first time the parent call is called. Otherwise the parent request fires and the switchMap one doesn't, here is the code:
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.subscribe(
...
)
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
Edit: Here is the entirety of the function that is called on click. when i click the button the first time for a given unit both methods are executed. If I try a Unit that has already had that method called (without a refresh) only the first method is executed. I realize a lot of this may not be clear it's a rather large project at this point.
public submitTenantCredit() {
this.isLoading = true;
let tenantCredit: NewTenantDebitCreditData;
let receivableDefinitions: ReceivableDefinition[] = [];
// construct receivable defintions for NewTenantDebitData model
receivableDefinitions = this._constructReceivableDefinitions();
// construct data we will be POSTing to server.
tenantCredit = new NewTenantDebitCreditData({
siteId: this._apiConfig.siteId,
leaseId: this.leaseId,
isCredit: true,
receivables: receivableDefinitions,
reason: this.actionReason
});
// make service call and handle response
this._receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result =>
// Refresh the lease receivables before giving result
this._receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
}).take(1).map(() => result)
)
.take(1)
.subscribe(
(receivables) => {
this.closeReasonModal();
let refreshLeaseId = this.leaseId;
this.leaseId = refreshLeaseId;
this.isLoading = false;
this.refreshBool = !this.refreshBool;
this._debitCreditService.refreshUnitInfo();
this._notifications.success(`The tenant credit for ${this.customerName} - Unit ${this.unitNumber} was submitted successfully`);
},
(error) => {
console.error(error);
this.isLoading = false;
}
)
}
If it helps newTenantDebitCredit() is a HTTP POST request and getAll() is a GET request.
You used take operator. When your service observable will emit then take operator will execute first and take will chain only first emit from observable. Subsequent emit will not taken by your code.
If you want to take all emits from observable then remove take from your code.
Hope it will help.
Testing the Rx code in isolation, here's a mockup. The console logs happen each time, so I think the Rx you're using is ok.
The best guess at a likely culprit is this.refreshBool = !this.refreshBool, but we'd need to see the internals of newTenantDebitCredit and getAll to be definitive.
// Some mocking
const _receivableService = {
newTenantDebitCredit: (tc) => {
console.log('inside newTenantDebitCredit')
return Rx.Observable.of({prop1:'someValue'})
},
getAll: (options) => {
console.log('inside getAll')
return Rx.Observable.of({prop2:'anotherValue'})
}
}
const tenantCredit = {}
// Test
_receivableService.newTenantDebitCredit(tenantCredit)
.take(1)
.switchMap(result => {
console.log('result', result)
return _receivableService.getAll({
refresh: true,
where: { leaseId: this.leaseId }
})
.take(1)
.map(() => result)
})
.take(1)
.subscribe(
(receivables) => {
console.log('receivables', receivables)
//this.refreshBool = !this.refreshBool;
},
(error) => {
console.error(error);
}
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.js"></script>
First of all, this has nothing to do with the switchMap operator.
Normaly removing the take(1) would cause this behaviour. In this case it wouldn't because it itsn't a so called hot observable.
The problem is that you are using a http.post. This is a cold observable which means it will only return a value once. That is also the reason why you don't need to unsubscribe. It will NEVER fire twice. Possible sollutions might be:
Using web sockets to get realtime data.
Creating a timer which will periodically fetch the data.
Simply get the data again whenever you need it.
The way you are asking the question
How can I make the getAll request in the switch map run every time the newTenantDebitCredit method is called above it?
actually sounds to me as if you are calling only newTenantDebitCredit from somewhere in your code, expecting the second request to happen; so I think this might be a misunderstanding of how observable chains work. Let's make an example:
const source$ = Observable.of(42);
source$
.map(value => 2 * value)
.subscribe(console.log);
source$
.subscribe(console.log);
What would you expect this to log? If your answer is "It would log 84 twice", then that is wrong: it logs 84 and 42.
Conceptually, your situation is the same. The second request only happens when the observable returned by newTenantDebitCredit() emits; it will not happen anytime some caller calls newTenantDebitCredit. This is because observable chains do not mutate an observable in-place, they only ever return a new observable.
If you want the second request to happen, you have to actually change the definition of the newTenantDebitCredit method to return an observable set up to perform the second request; alternatively, set up a chained observable that you subscribe to instead of calling newTenantDebitCredit.
Not really an answer but I did solve my problem. It will almost certainly be of no use to anyone BUT it was an issue in the receivableService it was not properly cheeking the boolean: refresh and was pulling values from cache after the first time.

Observables and response data typings

I'm trying to get a hang of Observables but sometimes get lost in nothing.
Assume we subscribe to getData to get json data asynchronously:
this.getData(id)
.subscribe(res => {
console.log(data.items[0])
// more data processing
})
This works, but processing response data inside .subscribe does not look pleasing. Assigning the response to a var seems like a way to go:
let data;
this.getData(id)
.subscribe(res => data = res)
console.log(data.items[0])
// more data processing
But in this case we get error since var 'data' has no initial type.
TypeError: Cannot read property 'items' of undefined
Creating an interface for the json response sounds silly. What am I missing?
Also, creating a callback function seems redundant as well, since it will take 2 functions to do the work of what supposed to be a single function.
since var 'data' has no initial type.
No. The error is because data is undefined. TypeScript is smart enough to see that data isn't initialized https://basarat.gitbooks.io/typescript/content/docs/javascript/recap.html. The order in which the code executes is given below:
let data; // 1
this.getData(id)
.subscribe(res => data = res) // 3!
console.log(data.items[0]) // 2
More
Please lookup async programming in JavaScript / TypeScript. Basically you can only use data once subscribe is called.

Categories

Resources