use values from observable in angular when they are ready - javascript

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

Related

Having promise response inside Vue template interpolation

I'm using Vue 2 and the composition API. My current component receives a prop from a parent and I render different data based on it, on the screen - the prop is called "result" and is of type Object, containing multiple info. I receive one "result" at a time, but multiple will be rendered - you can think of my "result" as a google search result - which means that the page will have multiple results.
The issue I have is that for one of the info inside "result", I need to call an asynchronous method and display its result, which is what I cannot accomplish.
Currently this is what I have:
<div>
{{ getBoardName(props.result.boardReference) }}
</div>
Method inside the script:
async function getBoardName(boardReference) {
var result = await entriesApi.getBoardName({
boardReference: boardReference,
});
return result;
}
It displays "[object Promise]", although if I console.log(result) right before returning it, it's just what I need, so it seems to me as if the interpolation doesn't actually wait for the promise result.
I've also tried using "then" inside the interpolation:
{{
getBoardName(props.result.boardReference).then((value) => {
return value;
})
}}
I was thinking about using a computed property but I am not sure how that would work, as the response I need from the method has to be connected to each result individually.
Please ask for further clarifications if needed.
As you thought, the interpolation does not actually wait for the result so this is why you have a Promise object.
Since you are using the composition API, what you can actually do is to use a reactive state (using the ref() function if you are waiting for a primitive type, which seems to be the case here, or the reactive() function for objects) and update the state within the onMounted() lifecycle hook.
setup(props, context) {
const boardGameName = ref("");
onMounted(async () => {
boardGameName.value = await getBoardName(props.result.boardReference);
});
async function getBoardName(boardReference) {
var result = await entriesApi.getBoardName({
boardReference: boardReference,
});
return result;
}
return {
boardGameName,
};
}
Since you are dealing with async data, you could add a loading state so you can show a spinner or something else until the data is available.
If your board reference changes over time, you could use a watcher to update your board game name.
Good luck with your project!

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

Angular: I have to fetch value from Observable<any> variable without using subscribe method, AIP should not be call again

Angular: I have to fetch value from Observable variable without using subscribe method, AIP should not be call again
My Code Snippet (Part Of Component Script):
....
....
this.serviceVariable.currentProgramStudents = this.serviceVariable.getAllStudents(); //API Call In This Service Function & Need this variable further, could not skip it
this.serviceVariable.currentProgramStudents.pipe(
take(1)
).subscribe(students => {
this.store.dispatch({ type: SET_STUDENTS, payload: students });
});
....
....
Working properly, but "take(1)).subscribe" causing API call again, which I want to skip.
I have to get value from that Observable variable without subscribe method or API call.
Please suggest me changes.

Best practise to handle with responses and incoming props

with redux, we uses actions to handle with crud operations. But I stuck at some points. If we send async requests inside of component. We can easly handle with response. But when we send request through actions, we dont know what happened. Is request send successfully ? it took how much amount of time ? What kind of response is returned ? we don't know that
I will clarify question with samples..
lets update a post.
onClick () {
postsApi.post(this.state.post) | we know how much time
.then(res => res.data) | has took to execute
.then(res => { | request
console.log(res) // we have the response
})
.catch(err => console.log(error))
}
But if we use actions
onClick () {
this.props.updatePost(this.state.post) // we know nothing what will happen
}
or handling with incoming props. lets say I have fetchPost() action to retrieve post
componentDidMount(){
this.props.fetchPost()
}
render method and componentDidUpdate will run as well. It's cool. But what if I want to update my state by incoming props ? I can't do this operation inside of componentDidUpdate method. it causes infinity loop.
If I use componentWillUpdate method, well, things works fine but I'm getting this warning.
Warning: componentWillReceiveProps has been renamed, and is not
recommended for use. Move data fetching code or side effects to
componentDidUpdate. If you're updating state whenever props change,
refactor your code to use memoization techniques or move it to static
getDerivedStateFromProps
I can't use componentDidUpdate method for infinty loop. Neither getDerivedStateFromProps method because it's run everytime when state change.
Should I continue to use componentWillMethod ? Otherwise what should I use and why (why componentWillMethod is unsafe ?)
If I understand correcty, what you would like to do is to safely change your local state only when your e.g. updatePost was successful.
If indeed that is your case, you can pass a callback function on your updatePost and call this as long as your update was succefull.
successfulUpdate() {
// do your thing
this.setState( ... );
}
onClick () {
this.props.updatePost(this.state.post, this.successfulUpdate) // we know nothing what will happen
}
UPDATE:
You can also keep in mind that if your action returns a promise, then you can just use the then method:
onClick () {
this.props.updatePost(this.state.post).then(this.onFulfilled, this.onRejected)
}
I think we can use redux-thunk in this cases. What if we dispatch an async function instead of dispatch an action object?
"Neither getDerivedStateFromProps method because it's run everytime when state change." - does it matter? You can avoid setting state with every getDerivedStateFromProps call by using a simple condition inside.
Example:
static getDerivedStateFromProps(props, state) {
if (props.post !== state.post) { // or anything else
return {
post: props.post,
};
}
return null;
};
An infinite loop will not occur.
Here is my way for such cases. We can redux-thunk for asynchronous calls such as api call. What if we define the action that returns promise? Please check the code below.
actions.js
export const GetTodoList = dispatch => {
return Axios.get('SOME_URL').then(res => {
// dispatch some actions
// return result
return res.data;
});
}
TodoList.js
onClick = async () => {
const { GetTodoList } = this.props;
try {
const data = await GetTodoList();
// handler for success
this.setState({
success: true,
data
});
} catch {
// handler for failure
this.setState({
success: fail,
data: null
});
}
}
const mapStateToProps = state => ({
GetTodoList
});
So we can use actions like API(which returns promise) thanks to redux-thunk.
Let me know your opinion.

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

Categories

Resources