use async properly in typescript (angular) - javascript

I have a list of department, which will be fetched from an API link.
When my angular app loads (I means after ngOnInit(): void call), I want :
I will fetch all department as async
Then I will await for all department to load
Finally, I will save 1st department's id in a variable
I am in confusion where I should put my await
(Note: my code is working, API response comes to me, I am only stuck in the await part)
Here is how far I tried:
app.component.ts:
import { Component, OnInit } from '#angular/core';
import { DepartmentModel } from '../../user/models/individual-department.model';
#Component({
selector: 'app-requisition-report-one',
templateUrl: './requisition-report-one.component.html',
styleUrls: ['./requisition-report-one.component.css']
})
export class RequisitionReportOneComponent implements OnInit {
displayIndividualDepartmentList: DepartmentModel[] = [];
currentDisplayDepartment: number = null;
constructor(private userService: UserService) { }
ngOnInit(): void {
this.initializeDepartmentList();
}
initializeDepartmentList() {
this.userService.GetAllIndividualDepartment().subscribe(
async (data: DepartmentModel[]) => {
this.displayIndividualDepartmentList = data;
this.currentDisplayDepartment = await this.displayIndividualDepartmentList[0].department_id;
}
);
}
onShowButtonClick() {
console.log(this.currentDisplayDepartment);
}
}
My vscode is giving me a warning like this
'await' has no effect on the type of this expression.ts(80007)
Besides, on show button click, I am getting null value.
I want to get the first department's id in this variable currentDisplayDepartment from the list displayIndividualDepartmentList after awaiting for the list to be loaded.
TIA

RxJS observables don't rely on async/await feature, but rather on a subscription/callback pattern. Your callback function will be executed everytimes the observable returned by GetAllIndividualDepartment method from UserService emits a new value, and therefore does not require any async prefix. You can access data in a synchronous way with no need to use the await keyword.
initializeDepartmentList() {
this.userService.GetAllIndividualDepartment().subscribe(
(data: DepartmentModel[]) => {
this.displayIndividualDepartmentList = data;
this.currentDisplayDepartment = this.displayIndividualDepartmentList[0].department_id;
}
);
}
A quick explanation of RxJS observables and how they are different from the Promise you are used to work with.

You can convert your Observable to a promise as below and use the async/await syntax as opposed to .then() syntax.
The toPromise() method will handle the subscribe/unsubscribe for you and will only emit when the observable has completed. In the case of HTTP observables they typically only emit one value (primitive or otherwise) and then complete.
You can then use async/await syntax as shown or use .then().
Using toPromise() with HTTP observeables is a very compelling and clean syntax making asynchronous code look synchronous and easier to follow.
async initializeDepartmentList(): Promise<void> {
const data: DepartmentModel[] = await this.userService.GetAllIndividualDepartment().toPromise();
this.displayIndividualDepartmentList = data;
this.currentDisplayDepartment = this.displayIndividualDepartmentList[0].department_id;
}
Call like this...
async ngOnInit(): Promise<void> {
await this.initializeDepartmentList();
.
.
}
Or with a self invoking function like this.
ngOnInit()
{
(async () => {
await this.initializeDepartmentList();
})()
}
Just to add that toPromise is being deprecated in RxJS 7 and will be removed in RxJS 8.
In its place you can use lastValueFrom
const data = await lastValueFrom(this.userService.GetAllIndividualDepartment());
As a side note, if you want to initialise your application upon startup you can use...
app.module.ts
providers: [
{
provide: APP_INITIALIZER,
useFactory: appInitializeService,
multi: true
},
Something like this. A promise returning void. The app effectively waits for your prerequisites to load before continuing.
export const appInitializeService = (): () => Promise<void> => (): Promise<void> => new Promise((resolve) => resolve());

Related

How to wait for new data from service before proceeding in Angular

I have two components that share data using a service:
export class ShareDataService {
msg: BehaviorSubject<string>;
constructor(
) {
this.msg = new BehaviorSubject("Default Message")
}
changeMessage(message: string) {
this.msg.next(message)
}
}
First component works as a router, when i click a name it redirects to the profile of that name using its id. I import the share-data service i created above, when clicking a name it triggers de sendData() function which gets the id of the clicked element and passes it as a parameter on the service changeMessagge() function:
constructor(
private _data: ShareDataService
) { }
message: string = "";
ngOnInit(): void {
this._data.msg.subscribe(new_msg => this.message = new_msg)
}
sendData() {
var self = this
$("ul.users-list").on("click", ".user", function(event) {
self._data.changeMessage(event.target.id)
});
}
}
After that, when it redirects to the new component, this new component also imports data from the service:
message: string = "";
constructor(
private _http: HttpClient,
private _data: ShareDataService,
private _router: Router
) { }
async ngOnInit() {
this._data.msg.subscribe(new_msg => this.message = new_msg)
await new Promise(r => setTimeout(r, 1)); // Sleeps 1ms so it can load message
if (this.message === "Default Message") { // Returns to landing if no user loaded
this._router.navigateByUrl("/landing");
return
}
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${this.message}`)
.subscribe((data: UserInterface) => this.user = data)
}
As you can see, it subscribes to the service and gets the value of messagge, which should have been changed when i clicked a name on the other component. The issue is, it fristly doesn't detect the change, and i have to use the new Promise() function to make it sleep for 1ms so it can load the new value of messagge from the service and then continue. Is there any way i can make it wait for the subscribe to finish loading the new messagge without using the Promise for 1ms? already tried putting the code inside the subscribe after it assigns messagge.
I see multiple issues
The variable this.message is assigned asynchronously. By the time the HTTP request is sent, it isn't assigned a value yet. You need to use higher order mapping operator like switchMap to map from one observable to another.
import { switchMap } from 'rxjs/operators';
this._data.msg.pipe(
switchMap(message =>
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
)
).subscribe((data: UserInterface) => this.user = data)
You could see my post here for brief info on handling multiple co-dependent observables.
await new Promise(r => setTimeout(r, 1)); looks inelegant however small the timeout be. With the above mapping operator it shouldn't be needed anymore.
Avoid using jQuery in Angular. Almost everything from jQuery could be accomplished directly in Angular. Mixing them might lead to maintenance later.
Update: returning observable conditionally
Based on your condition, you could use RxJS iif function with new Observable() construct to either return the HTTP request or do something else and close the observable based on a condition.
Try the following
import { iif, EMPTY, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
this._data.msg.pipe(
switchMap(message =>
iif(
() => message === "Default Message",
new Observable(subscriber => {
this._router.navigateByUrl("/landing");
subscriber.complete();
}),
this._http.get<UserInterface>(`https://jsonplaceholder.typicode.com/users/${message}`)
)
)
).subscribe((data: UserInterface) => this.user = data)

TypeScript : Value assigned inside HTTPClient scope is not accessible from outside (within the same function)

On Load, I'm calling the function getData() which will read & map the response to Data[] model and returns back the result. But the function returning undefined object though it's value is assigned.
import { HttpClient} from '#angular/common/http';
import {Data} from "../model";
export class Service {
constructor(private http: HttpClient) {}
getData():Data[]{
var data: Data[];
this.http.get(url,{ responseType: 'blob'}).subscribe(response => {
response.text().then(value => {
data = <Data[]>JSON.parse(value);
console.log(data); ----> {Printing the values as expected i.e., Data Array}
});
});
console.log(data); ----> {Printing Undefined}
return testData;
}
onLoad(){
var data:Data[] = this.getData();
console.log(data) ---------> {Printing Undefined}
}
}
Data Class :
export class Data{
id:number;
name:string;
value:string;
}
Http calls are async calls. Subsequent call will get executed and will not wait for the promise to be resolve.
Your dependent code should be written once it resolved.
I'll just show what I would like to do, here's a little bit reconstruction for your codes:
import {from, Observable} from 'rxjs'; // <= several imports you might care
import {map, switchMap} from 'rxjs/operators'; // <= several imports you might care
export class Service {
constructor(private http: HttpClient) {}
getData(): Observable<Data[]> {
return this.http.get(someurl, {responseType: 'blob'})
.pipe(
switchMap((blob: Blob) => {
return from(blob.text());
}),
map(text => {
return JSON.parse(text) as Data[];
})
);
}
onLoad(): void {
this.getData().subscribe((data: Data[]) => {
console.log(data); // <=== this is where you can log data
});
}
}
The basic idea is you should return a aync(aka Observable) result for your http request, and pipe the first result(type of Blob) to another Observable(which is built from a promise) of string value, then map it to a Data array at last, in onLoad you should subscribe the async result and check the data in subscription. In a word, you should keep the data flow asynchronously until it is been resolved

Angular RXJS, finish observable before return

i want to know how to 'wait' without using async/await to finish observable and then return that value
// import {Observable, of} from 'rxjs';
getFirstQueue() {
if (this.insideVar) {
return of(this.insideVar);
}
this.http.get(this.createUrl(`some_resource`)).subscribe((someVar: any) => {
this.insideVar = someVar;
});
return this.insideVar;
}
You can't return from a subscription. You have to return an Observable. A function higher up the stack will then subscribe to this observable.
You can use tap to perform the same side-effect you are currently doing in the subscribe body before returning.
getFirstQueue(): Observable<any> {
if (this.activeQueue) {
return of(this.activeQueue);
}
return this.http.get(this.createUrl(`some_resource`)).pipe(
tap((someVar: any) => {
this.insideVar = someVar;
})
);
}
It is not possible to go from asynchronous callback to synchronous context. For that you need to use async/await, but if you take a look at the decompiled code the async/await is just a syntactic sugar on top of Promises, so it only looks like it came from async to sync

How to await an asynchonous method defined inside subscribe before to complete an Rxjs Observable?

Let's take the code bellow:
myObservable.subscribe({
next: async () => {
await something();
},
complete: () => {
console.log('All async task are comlpeted');
}
});
The problem is that the console.log is called after the last next is triggered but I want it to be called after the last something() is finished.
Is there any way to do that ?
I specify that I implemented the myObservable by myself using new Obsercable(observer => ...). So it could be modified.
I would either a) stick to the observable recipe, and convert promises to observables, or b) stick to the promise/async-await pattern, and convert observables to promises. I frankly have no idea how to successfully mix those two.
Rx-based solution:
import { from } from 'rxjs';
import { finalize } from 'rxjs/operators';
myObservable.pipe(
switchMap(() => from(something()),
finalize(() => console.log('All async task are comlpeted')),
).subscribe(someFunction);

exporting after promise finishes

I would like to export a class which initial state depends on a value returned from a Promise in another module i cannot modify.
Here's the code:
let e = true;
APromiseFromAnotherModule()
.then(value => return value;);
export default class E {
constructor() {
if (e) {
//...
} else {
//...
}
}
}
I also tried with async/await encapsulating the Promise into an async function like this:
let e = true;
getInitialValue = async () => {
return await APromiseFromAnotherModule()
.then(value => e = value;);
};
e = getInitialValue();
export default class E {
constructor() {
if (e) {
//...
} else {
//...
}
}
}
But it doesn't make sense because that one is an async function so obviously it doesn't work.
What am I missing?
module exports are done synchronously. So, they cannot depend upon the results of an asynchronous operation.
And, await only works inside a function. It doesn't actually block the containing function (the containing function returns a promise) so that won't help you make an async operation into a synchronous one either.
The usual ways to deal with a module that uses some async code in its setup is to either export a promise and have the calling code use .then() on the promise or to initialize the module with a constructor function that returns a promise.
The code is only pseudo code so it's hard to tell exactly what your real problem is to show you specific code for your situation.
As an example. If you want to export a class definition, but don't want the class definition used until some async code has completed, you can do something like this:
// do async initialization and keep promise
let e;
const p = APromiseFromAnotherModule().then(val => e = val);
class E {
constructor() {
if (e) {
//...
} else {
//...
}
}
};
// export constructor function
export default function() {
return p.then(e => {
// after async initialization is done, resolve with class E
return E;
});
}
The caller could then use it like this:
import init from 'myModule';
init().then(E => {
// put code here that uses E
}).catch(err => {
console.log(err);
// handle error here
});
This solves several issues:
Asynchronous initialization is started immediately upon module loading.
class E is not made available to the caller until the async initialization is done so it can't be used until its ready
The same class E is used for all users of the module
The async initialization is cached so it's only done once
Edit in 2023. Modern nodejs versions when using ESM modules have top level await so it is possible to await an asynchronous result before your exports.
I understand that #jfriend00's answer works fine, but in my case, there is not just one block of code that depends on the foreign async function's completion.
In my app, configuration loading is asynchronous, so the server must wait for the configs to load before starting. Since there are other files (like routes) that compose the server that also need access to the configs, I would have to reluctantly call .then() in each other file.
Here is how I was able to do it with require statements instead of export.
config.js
Module that can be required by other modules in order to gain access to the global configs.
module.exports.loadAllConfigs = async () => {
const appConfigs = await AsyncConfigLibrary.load();
module.exports.CONFIG = appConfigs;
};
server.js
Main file for Node application that makes use of other modules that require access to global configs.
const { loadAllConfigs } = require('./modules/config');
loadAllConfigs()
.then(() => {
const { CONFIG } = require('./modules/config');
/* import modules */
const auth = require('./modules/auth');
};
auth.js
Module used by server.js that requires access to configs.
const { CONFIG } = require('./config');
const cookieSecret = CONFIG.secretItem;
Therefore, as long as the CONFIG property is set in config.js before any of the other modules attempt to access it, that is, before the modules are require'd, then the single .then() in server.js is sufficient.

Categories