Angular 8: Run Component Class Methods Synchronously/Sequentially - javascript

How do I run methods synchronously in Angular Typescript?
These are not functions, but methods.
First one calls a service, and then second saves into an array.
runTwoMethods()
{
this.validateAddress();
this.saveJsonArchive();
}
Validate address may call more sub-method,may not even be Api, so want to wait until everything completes before saving.
Following syntax is for functions, currently searching for class methods,
Angular / TypeScript - Call a function after another one has been completed
At the end, data is stored into a current data object. And I want to be
save to an archive. Maybe another possibility, how do I keep AddressCurrentMailing and JSONArchive[2] in sync?
Current Data object is sourced from API, not sure where (nor I am not allowed to edit the APIs, then calls transformations), and then would like to save into JsonArchive.
this.jsonArchive[this.jsonArchiveCounter] = this.addressCurrentMailingFinalData

You can use Observable for the 1st and use your 2nd method after Subscribe in callback:
validateAddress(): Observable<Someclassname>
{
return this._http.get<Someclassname>('url');
}
this.yourService.validateAddress().subscribe(
(result) => {
this.saveJsonArchive();
},
(error) => console.log('NO saveJsonArchive'));
EDIT (validateAddress with more one Observable)
const ox: Observable<Someclassname>[] = [];
ox.push(this.http.get<Someclassname>(url1));
ox.push(this.http.get<Someclassname>(url2));
ox.push(this.http.get<Someclassname>(url3));
forkJoin(ox).subscribe(result => this.saveJsonArchive());

You could return a Promise in "this.validateAddress();" and await it.
I assume you use the Angular HttpClient.
For example:
private validateAddress(): Promise<someclassname>
{
return this._http.get<someclassname>("someurl").toPromise()
}
async addSeasonal()
{
await this.validateAddress();
await this.saveJsonArchive();
}
I hope this helps.

Related

In Angular multiple http requests fire when I redirect from one component to another component (in increasing order), do we have any solution for it?

In my project there is one API which is called on init method when component is called as follows:
ngOnInit(): void {
this.getCurrentUserpost();
}
and the function looks like this
getCurrentUserpost(){
this.friend.getAllFriendsPost(this.friendsId).subscribe(res => {
this.allPosts = res;
});
}
and the service API function looks like the following
getAllFriendsPost(ids:any){
return this.http.post(this.BaseUrl+'/allfriendspost',ids);
}
When I navigate between component using routeLink then every time when I visit component where I have added this API, it will call the API multiple times at the same time with increasing order.
See the network tap where you can see multiple API calls are done
Can anyone advise me how to I can resolve this issue?
You have a memory leak. You need to unsubscribe from the observable.
One of multiple possibilities would be to use take(1) rxjs-operator.
getCurrentUserpost(){
this.friend.getAllFriendsPost(this.friendsId).pipe(
take(1),
).subscribe(res => {
this.allPosts = res;
});
}
Just to mention some other possibilities:
async pipe
takeUntil operator (or other take*) operators
Subscription.unsubscribe

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: Ensure Services is Complete before Running Next Step

We are currently using Angular.
Component is receiving data from API. After getting API Data, it goes through Data Services which transform and customize the data, concatenate First Last Name, rounds dollar amounts, makes calculations, etc.
The last step tries to populate the Sales year in a Dropdown, after parsing all the data.
this.webStoreSearchHttpService.GetAllCustomerSalesData(this.customerId).subscribe((response) => {
this.customerList= customerDataService.createCustomerList(response);
this.productList = customerDataService.createProductAnalysis(response);
this.salesList= customerDataService.createSalesList(response);
this.salesYearList= customerDataService.createYearList(response);
this.salesYearItemCurrent = _.cloneDeep(this.salesYearList[0]); <--- this goes into a Mat Select Dropdown
However, correlating data does not appear after selecting web dropdown, because the Data Services is not finished parsing/created yet, even though its in original API subscribe.
What I am trying to do, is make sure all 4 Data services are totally complete, and Then populate salesYear. How can this be done with Angular typescript ?
The data services can be run in Parallel, however last step is salesYear population in dropdown.
The methods return class arrays, not promises or observables.
Update
You added the sentece The methods return class arrays, not promises or observables.. This implies that you have no possibility from outside to wait for asynchroneous calls to finish. Hence you have to change the return value of the customerDataService methods. I am assuming that inside this methods some asynchroneous stuff is done, because you say What I am trying to do, is make sure all 4 Data services are totally complete.
Old version
To answer your question one have to know what the customerDataService methods return type is. Do the method return Promise or Observable? Depending on that you can use Promise.all or forkJoin operator to wait for all methods to finish and then execute the select population. This is an example using observables:
this.webStoreSearchHttpService.GetAllCustomerSalesData(this.customerId).subscribe(response => {
forkJoin([
customerDataService.createCustomerList(response),
customerDataService.createProductAnalysis(response),
customerDataService.createSalesList(response),
customerDataService.createYearList(response)
]).subscribe(([customerList, productList, salesList, salesYearList]) => {
this.customerList = customerList;
this.productList = productList;
this.salesList = salesList;
this.salesYearList = salesYearList;
this.salesYearItemCurrent = _.cloneDeep(this.salesYearList[0]);
});
});
or even better to avoid the inner subscription and has only one subscription:
this.webStoreSearchHttpService.GetAllCustomerSalesData(this.customerId).pipe(
flatMap(response =>
forkJoin([
customerDataService.createCustomerList(response),
customerDataService.createProductAnalysis(response),
customerDataService.createSalesList(response),
customerDataService.createYearList(response)
])
)
).subscribe(([customerList, productList, salesList, salesYearList]) => {
this.customerList = customerList;
this.productList = productList;
this.salesList = salesList;
this.salesYearList = salesYearList;
this.salesYearItemCurrent = _.cloneDeep(this.salesYearList[0]);
});

How to wait for a variable to set in angular

AppInitializer class
dataloadcomplete = false;
initializeApp() {
let promise = new Promise((resolve, reject) => {
Promise.all(promise).then(
(res) => { dataloadcomplete = true; resolve() },
(err) => { reject(); },
);
return promise;
}
CanactivateRoute
canActivate(route: ActivatedRouteSnapshot): Promise<Boolean> {
// if data load completed return true else false;
}
want to reinitialize the application on country change.
With the small amount of shown code, it's difficult to suggest the best course of action as there are several options.
First, in any case, be sure that the class that holds the data (Class 1 in your example) is an injectable service. Inject it into any component that needs to access the data.
If you need to load some data and wait to route to a specific component until that data is loaded, you can use route resolvers.
If you need to wait for any of the application to load before launching the application, you can use APP_INITIALIZER: Angular: How to correctly implement APP_INITIALIZER
If you want to better manage your application state, consider using the Redux library for Angular called NgRx. You can use it to manage the flow of data in your application.
If you have multiple HTTP calls, I think, you can use forkJoin to retrieve all the call into one point then use EventEmitter and catch the event from the function of class 2. no need to wait for setting the variable of class 1.
You can use also interval in Angular and wait until you get the true value of the variable of class 2.

Categories

Resources