Observables and response data typings - javascript

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.

Related

How to get id of an object from firebase

I am trying to get id of an object after set that object. But I am getting type error. TypeError: Cannot read properties of undefined (reading 'val'). How should I do that with firebase 9?
Here is the code that I want to work:
set(push(ref(db, "expenses")), expense)
.then((snapshot) => {
console.log(snapshot.val());
dispatch(
addExpense({
id: snapshot.key,
...expense,
})
);
})
.catch((e) => {
console.log("This failed.", e);
});
Thanks in advance.
Why your code doesn't work
The documentation of set(ref, value) shows that is is defined as:
function set(ref: DatabaseReference, value: unknown): Promise<void>
It returns a Promise<void>, so there's no snapshot being passed to your then.
If the promise resolves (and thus your then callback gets called) that the expense was written to the database on the server as is.
How to fix it
If you want to get the key of the push call, you can capture that outside of the set call already:
const newRef = push(ref(db, "expenses"));
set(newRef, expense)
.then(() => {
dispatch(
addExpense({
id: newRef.key,
...expense,
})
);
})
.catch((e) => {
console.log("This failed.", e);
});
Calling push is a pure client-side operation, which is synchronous, so that doesn't require await or then (which should be used with asynchronous operations).
Further considerations
Note though that now you're only showing the expense locally after it's been written to the server. If that is a requirement for your use-case, then 👍. But when using Firebase it is quite common to:
Use a permanent, onValue listener on expenses to show the latest expenses in the UI.
Write the new expense with a simple call, without a then() listener: set(push(ref(db, "expenses")), expense);
The Firebase SDK will then immediately call the local onValue listener with the new value, with the assumption that the write will succeed.
So your UI will show the local value straight away, giving the user an almost instant response.
In the (more uncommon) case that the server (i.e. your security rules) rejects the write operation, the SDK calls onValue again with the corrected data, so your UI can update the state.

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);
}
});
}

Can't access array after passing it to state, but can access it before

I have an pseudo-object is inside my state. I've been able to access through a couple layers, but when I reach the array inside the object Im getting undefined errors.
UPDATE: Its something wrong with how I pass lambdaReturnObject to the state which isn't letting me access the array, tested with lambdaReturnObject.campaigns[0].campaignName and it worked.
handleSearch() {
//data to use to query backend
let campaignId = this.refs.campaignInput.value
let marketplace = this.refs.marketplaceInput.value
//using local copy of backend data, production should call backend fo this instead
let lambdaReturn = "{\"advertiser\":{\"advertiserId\":\"1\",\"enforcedBudget\":0.1},\"campaigns\":[{\"campaignID\":\"1\",\"campaignName\":\"fake\",\"createDate\":11111,\"creationDate\":1111,\"startDate\":1111,\"endDate\":1111,\"dailyBudget\":0.1,\"internal\":{\"budgetCurrencyCode\":\"USD\",\"inBudget\":true},\"enforcedBudget\":0.1,\"budgetCurrencyCode\":\"USD\",\"budgetPacingStrategy\":\"asp\",\"budgetType\":\"averageDaily\",\"status\":\"enables\",\"internalStatus\":\"enabled\"}],\"campaignID\":\"1\"}"
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})
}
When I try and go to the array inside, I get the following error
<h3>Campaigns :{console.log(this.state.apiData.campaigns[0].campaignName)}</h3>
Cannot read property '0' of undefined
This means I am accessing it the wrong way, but I looked at other posts (Accessing Object inside Array) and I thought that this was right. Though I am definitely wrong or else I wouldn't be writing this.
JSON.parse() is synchronous function, so set state wont be called till, JSON.parse() executes completely and returns the object.
Still You can try following
Call JSON.parse() using a try-catch block like below and see if it works. Also it is error free way of parsing your stringified objects.
try {
let lambdaReturnObject = JSON.parse(lambdaReturn)
this.setState({
apiData: lambdaReturnObject
})object
}
catch (err) {
// Do error handling here.
}
Use optional chaining, and try to access your object like this.state.apiData.campaigns?.[0].campaignName; this won't give error even if compaigns is undefined.
Refer : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
Depending on what is happening. The call to get campaigns[0] is getting resolved before the API call finishes. You can try a promise or async await to make sure the object is retrieved from the API call before you attempt to access it.
Do you happen to have a code snippet of is being used to get the data?
The error was that render() calls right as the page is booted up. At that time the object is not stored in my state so trying to call
this.state.objects.innerObject.property
would fail because I declared objects in my state, innerObject could be counted as null before the object is actually loaded in so it wouldn't throw an error. But property would throw an error because innerObject as we know, is null.
Fix by using an if statement before rendering the page to see if the object is actually loaded in. If not render just default empty JSX.

Http subscribe data not assigning to variable

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
);
}

Returning value from an observable operation in angular?

Am working in Angular and the problem seems to be: When I subscribe to the observable in my component, I can console.log() the data successfully, but I cannot assign the data to any afore declared variables and view it in the view template. This is the code: I understand that logging in the console is a synchronous process while the observable subscription in itself is asynchronous, but outputting a value from an asynchronous operation in the view template seems to be the problem. I have seen quite a number of solutions on stack overflow but it does not resolve the problem since it doesn't address this kind of problem.
This is a sample of the code
//The getData function returns an obsverbale
favoriteShirt;
const gtc = this;
gtc.getData().subscribe({
next: (data) => {
console.log(data.favShirtFromStore) // this returns an objects with the shirts (this is a sync op)
gtc.favoriteShirt = data.favShirtFromStore; //this returns undefined <= where the problem is
},
error:(err)=>{console.log(`There was an error ${err}`)},
complete:()=>{console.log("Completed...")}
});;
Why don't you use it like this:
gtc.getData().subscribe(res => {
//whatever you want to do with res
});
here res is returned data from your function and you can use it the way you want, such as assign it to another variable and so on ...

Categories

Resources