Use Promise function in Angular 6 Data Binding - javascript

I'm having an issue where I am using an async function in Angular 6 data binding:
<span *ngIf="application.status == 'in-progress'; else new">
{{getAssignedUser(application.user_id)}}
</span>
The getAssignedUser() is an async function which fetches a document from Firestore and I want to display the assigned user's firstname which I received from Firestore. The only problem here is I can't display the firstname value OnInit. If I insert a button and add a click event, it displays the name.
Component:
async getAssignedUser(id): Promise<string> {
if (id != null) {
return this._usersService
.getUserById(id)
.then(data => {
this.assignedUser = data.user_fname;
return this.assignedUser;
})
} else {
return null;
}
}
Service:
getUserById(id): any {
let user: any;
user = this.afs.collection('agents').doc(id).ref.get().then(function (doc) {
if (doc.exists) {
user = doc.data();
console.log(user);
return user;
}
else {
console.log('No such document');
}
}).catch(function (error) {
console.log('Error getting document: ', error);
})
return user;
}
Any help please?

Issue
The issue with making the call getAssignedUser from the html and expecting to return the value. This will not guarantee because getAssignedUser performs async operation. You had mentioned the async operation on getAssignedUser however does not returns any Observable or Promise.
Solution
You need to change in both services and component. Function should return the Promise to handle this case.
Service:
getUserById(id): any {
return new Promise((resolve, reject) => {
this.afs.collection('agents').doc(id).ref.get().then(function (doc) {
if (doc.exists) {
user = doc.data();
console.log(user);
resolve(user);
}
else {
resolve(null); //<-- you can reject if you want.
}
}
}
}
Component:
async getAssignedUser(id): Promise<string> {
return this._usersService
.getUserById(id);
}
html
<span *ngIf="application.status == 'in-progress'; else new">
{{getAssignedUser(application.user_id)} | async}
</span>
Important : You should not function in html, it may lead to multiple call and will impact on the performance. Better would be to use function instead.
Note : code is written directly to stackoverflow editor so there could be typo or syntactical error. So please correct yourself.

Agree with #Sunil Singh Above Answer, small correction in template file. async operator is mainly used for observable. You can call without that it should work.
<span *ngIf="application.status == 'in-progress'; else new">
{{getAssignedUser(application.user_id)}}
</span>

Related

Angular make an api call async and use the result

I'm trying to make an API call
ngOnInit(){
this.function1()
}
function1(){
this.userService.getUser()
.then((data) => {
this.user = data.user
this.userM = data.userM
// here problem: If I make a console.log(this.userM) it result at the beginning empty
if(this.userM.length > 0 ) {
console.log("goInside")}
else {
console.log("doesn't enter")
//it enters always there
}
}
}
in my service:
async getUser(): Promise<any>{
let user: any = []
let userM: any = []
//.... operation to populate
return {user, userM}
}
When I make an api call I would to use the this.userM array to make some operations. But it results always empty at the beginning, but it doens't enter anymore in if. How can I do?
Make use of rxjs, try using an Observable instead:
import { of } from 'rxjs';
getUsers(): Observable<any> {
let user: any = []
let userM: any = []
//.... operation to populate
return of({user, userM});
}
And then, within your component:
ngOnInit(){
this.function1()
}
function1(){
this.userService.getUser().subscribe((response) => {
const { user, userM } = response;
if (userM.length) console.log('There is data in it!')
else console.log('No data found...');
});
}
I suggest not using Promises in an Angular project unless necessary, you have at your disposal rxjs along with tons of useful operators for handling async calls/streams of data

Angular 9 can't make a post request

I'm currentry just a begginner in Angular,
I'm trying make a post request using this function:
signIn(user: User) {
console.log("1");
return this.http
.post<any>(`${this.endpoint}/login`, user)
.subscribe((res: any) => {
console.log("2");
localStorage.setItem("access_token", res.token);
this.getUserProfile(res._id).subscribe(res => {
this.currentUser = res;
this.router.navigate(["user-profile/" + res.msg._id]);
});
});
}
I tried to monitor the network (using the network tab in firefox), and I've found out that no data is sent.
When opening the console it displays "1" but not "2".
Thank you
You need to return the observable and subscribe to it there. Now you are returning a subscription so you might not know when actually the call is triggered. Also try to avoid nested subscriptions. You could use RxJS higher order operators (like switchMap) to pipe multiple observables. Try the following
some service
signIn(user: User) : Observable<any> {
return this.http.post<any>(`${this.endpoint}/login`, user).pipe(
switchMap((user) => {
localStorage.setItem("access_token", user.token);
return this.getUserProfile(user._id);
}
);
}
component
ngOnInit() {
this.someService.signIn(user).subscribe(
res => {
this.currentUser = res;
this.router.navigate(["user-profile/" + res.msg._id]);
},
(error) => {
// always good practice to handle HTTP observable errors
}
);
}

How to return value from firestore database?

I am having a hard time returning a value from the firestore database.
I am trying to return the 'amount' from the database.
When setting the variable i am able to console.log the 'amount'. (see code)
But when i try to return the value at the end of the function, it does not return anything.('amount' is not defined no-undef)
How can i return this value. Any help would be great. Please keep in mind that i am still quite new to this topic.
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';
export default function checkAmount() {
let user = firebase.auth().currentUser;
if (user) {
let userUID = user.uid
let docRef = firebase.firestore().collection("users").doc(userUID);
docRef.get().then((doc) => {
if (doc.exists) {
let amount = doc.data().amount;
if (amount > 0){
console.log(amount) /// This does work
}
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
}
return amount /// This **does not** return anything . How do i return the amount?
}
The reason is because the get() method is asynchronous: it returns immediately with a promise that resolves some time later with the results of the query. The get() method does not block the function (it returns immediately, as said above): this is why the last line (return amount) is executed before the asynchronous work is finished but with an undefined value.
You can read more here on asynchronous JavaScript methods and here on why Firebase APIs are asynchronous.
You therefore need to wait that the promise returned by the get() resolves and use the then() method, as Alex mentioned, to receive the query results and send the response.
The following will work:
export default function checkAmount() {
let user = firebase.auth().currentUser;
if (user) {
let userUID = user.uid
let docRef = firebase.firestore().collection("users").doc(userUID);
return docRef.get().then((doc) => { //Note the return here
if (doc.exists) {
let amount = doc.data().amount;
if (amount > 0){
console.log(amount) /// This does work
return true; //Note the return here
}
} else {
console.log("No such document!");
//Handle this situation the way you want! E.g. return false or throw an error
return false;
}
}).catch(error => {
console.log("Error getting document:", error);
//Handle this situation the way you want
});
} else {
//Handle this situation the way you want
}
}
But you need to note that your function is now also asynchronous. Therefore you should call it as follows:
checkAmount().
then(result => {
//Do whatever you want with the result value
console.log(result);
})

How do I ensure I won't replace a content to an old response?

Good day for all,
I am doing a React course and I'd submited the code to the reviewer. He's returned me few comments and there is one comment I'm not being able to solve.
The comment is the following:
Check if (query === this.state.query) to ensure you are not going to replace the contents to an old response
And part of the code is the one below:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Anyone could help me what do am I suppose to do?
Regards.
Code reviewer is right, you don't really want to replace the response if user has entered the very same query.
You have to store somewhere what for user has searched recently:
this.setState({ wantedBooks: [], query });
In case of success response:
this.setState({ wantedBooks, query });
And then check it in case of further searches:
if (query && query !== this.state.query) {
// continue the search only if query is different that current
Instead of relying on an outer member which is open to abuse by other code, you can employ a factory function to more safely trap a member.
As you have discovered, trapping and testing query == this.state.query can be made to work but is arguably not the best solution available.
With a little thought, you can force each call of updateWantedBooks() automatically to reject the previous promise returned by the same function (if it has not already settled), such that any success callbacks chained to the previous promise don't fire its error path is taken.
This can be achieved with a reusable canceller utility that accepts two callbacks and exploits Promise.race(), as follows:
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject }) // rejectable promise
]).then(successCallback);
};
};
Here's a demo ...
// reusable cancellation factory utility
function canceller(work, successCallback) {
var cancel;
return async function(...args) {
if (cancel) {
cancel(new Error('cancelled')); // cancel previous
}
return Promise.race([
work(...args),
new Promise((_, reject) => { cancel = reject })
]).then(successCallback);
};
};
// delay utility representing an asynchronous process
function delay(ms, val) {
return new Promise(resolve => {
setTimeout(resolve, ms, val);
});
};
function MySpace() {
// establish a canceller method with two callbacks
this.updateWantedBooks = canceller(
// work callback
async (query) => delay(500, query || { 'error': true }), // a contrived piece of work standing in for BooksAPI.search()
// success callback
(wantedBooks => this.setState(wantedBooks)) // this will execute only if work() wins the race against cancellation
);
this.setState = function(val) {
console.log('setState', val);
return val;
};
};
var mySpace = new MySpace();
mySpace.updateWantedBooks({'value':'XXX'}).then(result1 => { console.log('result', result1) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks(null).then(result2 => { console.log('result', result2) }).catch(error => { console.log(error.message) }); // 'cancelled'
mySpace.updateWantedBooks({'value':'ZZZ'}).then(result3 => { console.log('result', result3) }).catch(error => { console.log(error.message) }); // {'value':'ZZZ'} (unless something unexpected happened)
Note that canceller() doesn't attempt to abort the asynchronous process it initiates, rather it stymies the success path of the returned promise in favour of the error path.
I think reviewer's point is that response of Search API is asynchronous and result for "query 1" can arrive after user changed his mind and already requested search "query 2". So when response arrive - we need to check if we really interested in it:
updateQuery = query => {
this.setState({
query: query
wantedBooks: []
})
this.updateWantedBooks(query);
}
updateWantedBooks = query => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
// if updateQuery("query1) and updateQuery("query2") called in a row
// then response for query1 can arrive after we requested query2
// => for some period of time we'll show incorrect search results
// so adding check if query still the same can help
if (query !== this.state.query) {
// outdated response
return;
} else if (wantedBooks.error) {
// query is okay, but server error in response
this.setState({
wantedBooks: []
})
} else {
// success response to requested query
this.setState({ wantedBooks });
}
})
}
}
Guys I´ve done some tests with your answers, but I realize that somehow the code was behavioring strangely.
So, I've seen in other part of the reviewer comments, a part which I hadn't had seen before do my answer here, the following comment:
Inside 'then' part of the promise check if(query === this.state.query) to ensure you are not going to replace the contents to an old response.
And this "Inside 'then'" has been beating in my brain.
So, I think I've arrived in a satisfatory code; sure, maybe it isn't the definite solution, that's why I want to show here for you and feel free to comment if I'd have to make some improvement. Here below I put the code:
updateQuery = (query) => {
this.setState({
query: query
})
this.updateWantedBooks(query);
}
updateWantedBooks = (query) => {
if (query) {
BooksAPI.search(query).then((wantedBooks) => {
if (wantedBooks.error) {
this.setState({ wantedBooks: [] });
} else if (query !== this.state.query) {
this.setState( { wantedBooks: [] });
} else {
this.setState({ wantedBooks: wantedBooks });
}
})
} else {
this.setState({ wantedBooks: [] });
}
}
Regards

How to properly implement mongodb async/await inside a promise?

I've read that having an async inside a Promise is anti-pattern for async/await. The code below works, but I am curious how else to achieve the same result without having async in Promise.
If I remove it, the linter would tell how I can't use await in my mongodb query. If I remove the await in the mongodb query, then it wouldn't wait for the result.
export const getEmployees = (companyId) => {
return new Promise(async (resolve, reject) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
resolve({
employees,
});
});
Thanks.
async functions automatically return Promises already, which resolve with whatever expression is eventually returned. Simply make getEmployees an async function:
export const getEmployees = async (companyId) => {
const employees = await Employees.find(
{ companyId },
);
// other logic here...
return { employees };
};
(but make sure to catch in the consumer of getEmployees just in case there's an error)
As #CertainPerformance answered, that is perfect way to retrieve data from mongoDB using async/await, I would like to add some more information about how to handle errors in this case for correctness of the system, and better error handle to return better status to the client about his request.
I'd say it , you usually want to catch all exceptions from async/await call.
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
Now let's check the ways we can handle our errors that might occur.
Handle error inside error scope.
Assign a default value to the variable in the catch block.
Inspect error instance and act accordingly.
This is the most common way to handle errors in those cases and most elegant way in my opinion.
Handle error inside error scope:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
console.error(error);
}
};
Assign a default value to the variable in the catch block:
export const getEmployees = async (companyId) => {
let employees;
try {
employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
employees = employees;
} catch (error) {
console.error(error);
}
if (employees) { // We received the data successfully.
console.log(employees)
// Business logic goes here.
}
return employees;
};
Inspect error instance and act accordingly:
export const getEmployees = async (companyId) => {
try {
const employees = await Employees.find({
companyId
});
// You can add more logic here before return the data.
return {
employees
};
} catch (error) {
if (error instanceof ConnectionError) {
console.error(error);
} else {
throw error;
}
}
};
Some more explanations about async await and more useful methods that you can find in those answers.
How run async / await in parallel in Javascript

Categories

Resources