I am trying to return an observable inside an async arrow function passed to a flatMap, but the returned observable is not being called.
protected buildUseCaseObservable(params: LoginUserParams): Observable<Session> {
return this.userRepository.getUserByName(params.getUsername())
.pipe(flatMap(async user => {
if (!user) {
throw new Error(Errors.USER_DOESNT_EXIST);
}
const match = await this.cypher.compare(params.getPassword(), user.password);
if (!match) {
throw new Error(Errors.WRONG_PASSWORD);
}
return Observable.create((subscriber: Subscriber<Session>) => {
subscriber.next(new Session("token test", "refreshToken test"));
subscriber.complete();
});
}));
}
Does anyone knows why does it happen and how can I solve it? Thanks in advance.
Solved, I just turned the promise into an observable and did flatMap it.
protected buildUseCaseObservable(params: LoginUserParams): Observable<Session> {
return this.userRepository.getUserByName(params.getUsername())
.pipe(flatMap(storedUser => {
if (!storedUser) {
throw new Error(Errors.USER_DOESNT_EXIST);
}
return from(this.cypher.compare(params.getPassword(), storedUser.password));
})).pipe(flatMap(match => {
if (!match) {
throw new Error(Errors.WRONG_PASSWORD);
}
return Observable.create((subscriber: Subscriber<Session>) => {
subscriber.next(new Session("token test", "refreshToken test"));
subscriber.complete();
});
}));
}
Related
I am building a sign in functionality using bloc pattern, if the entered credentials are invalid, bloc will return a authErrorState, so I will display a invalid credentials popup as soon as the bloc return a authError State
please check the code :
if (state is IsAuthLoadingState) {
return const LoadingSpinnerWidget();
} else if (state is IsAuthenticatedState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
stopTimer();
BlocProvider.of<AuthBloc>(context).add(LoadAuthStatus());
Navigator.pop(context, true);
});
} else if (state is AuthErrorState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
stopTimer();
showCustomPopUp(state.message);
});
}
Bloc code :
void _onLoginUser(LoginUser event, Emitter<AuthState> emit) async {
emit(IsAuthLoadingState());
final UserLoggedInResponse userDetails =
await authRepository.handleLoginUser(event.phoneNumber, event.otp);
if (userDetails.status == "success") {
for (var item in userDetails.wishlist) {
await _localRepo.addWishlistItem(item);
}
for (var item in userDetails.cart) {
await _localRepo.addCartItem(item);
}
for (var item in userDetails.recentSearches) {
await _localRepo.addRecentSearchTerm(item);
}
await _localRepo.addPurchasedItems(userDetails.purchasedItemIds);
await _localRepo.setIsAuthenticated(
userDetails.accessToken, userDetails.userId);
emit(IsAuthenticatedState());
} else {
emit(AuthErrorState(
message: userDetails.message, content: userDetails.content));
}
}
But, the invalid credentials popup written in authErrorState is getting called multiple times.
Any help is really appreciated. Thank you.
As I didn't found any alternative options, I someone tried to manage this for now like this,
I used a bool variable called isErrorShown, and it was set to false by default,
once the code in widgetsBinding is executed, it will set the isErrorShown to true, function is widgetsBinding checks the value of isErrorShown and executes only if it is false :
else if (state is AuthErrorState) {
print("error state");
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!isErrorShown) {
stopTimer();
if (state.message ==
"user does not exits, please create user") {
Navigator.pushReplacementNamed(context, '/create-user',
arguments: CreateUserPage(
showProfile: widget.showProfile,
phoneNumber: phoneNumberController.text,
otp: otpController.text,
));
// BlocProvider.of<AuthBloc>(context).add(LoadAuthStatus());
// Navigator.pushNamed(context, '/create-user');
} else {
showCustomPopUp(state.message);
}
isErrorShown = true;
}
});
I am looking for simply fetching an object as a JSON, not as an observable.
So far I could:
fbGetCarById(car_id: string){
var car_json;
var car_obs: FirebaseObjectObservable<any>;
car_obs = this.db.object('/cars/'+car_id, { preserveSnapshot: true });
car_obs.subscribe(snapshot => {
console.log(snapshot.val())
car_json = snapshot.val();
});
return car_json;
}
However, .subscribe is an async function which does not return the snapshot linearly, so everything ends a mess =/.
How can I simply look for an object and have a simple JSON object as a response?
You could return a Promise.
fbGetCarById(car_id: string):Promise<string>{
const promise = new Promise<string>((resolve,reject)=>{
var car_obs: FirebaseObjectObservable<any>;
car_obs = this.db.object('/cars/'+car_id, { preserveSnapshot: true });
car_obs.subscribe(snapshot => {
resolve(snapshot.value())
});
}
return promise;
}
I try to consume an observable through an async pipe, here is my template :
<input #address type="text" id="address" placeholder="{{placeHolder}}" (keyup)="addTerm(address.value)" />
<div *ngFor="let place of possiblesPlaces | async">
{{place.label}}
</div>
Here is the component part :
export class LocationFieldComponent implements OnInit{
private searchTerms = new Subject<string>();
#Input()
placeHolder:string;
possiblesPlaces:Observable<Array<{label:string,value:any}>>;
addTerm(address:string) {
this.searchTerms.next(address);
}
ngOnInit(): void {
this.possiblesPlaces=this.searchTerms.filter(t=>t.length>2).debounceTime(300).distinctUntilChanged()
.flatMap(term =>
Observable.of(this.fetchResults(term))
)
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
this.possiblesPlaces.subscribe(val => console.log(val))
}
fetchResults(address:string) :Array<{label:string,value:any}> {
var result=new Array;
var geocoder = new google.maps.Geocoder();
console.log("search with"+address);
geocoder.geocode( { 'address': address}, (results, status) => {
if (status == google.maps.GeocoderStatus.OK) {
for(let i=0;i<results.length;i++){
result.push( {label:results[i].formatted_address,value:null});
}
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
return result;
}
}
I can see all the new values of the possiblesPlaces oberservable on the console but the async pipe won't show any results.
Sometimes i can see the results of the previous observable value for half a second when typing a new letter in the address field, i don't understand why?
EDIT: i discovered if i wait like 20 seconds, the results appear correctly. The request to the google api is quick but the async pipe seems to take a while. Any ides?
This should work
fetchResults(address:string) :Array<{label:string,value:any}> {
var subj = new Subject();
var geocoder = new google.maps.Geocoder();
console.log("search with"+address);
geocoder.geocode( { 'address': address}, (results, status) => {
if (status == google.maps.GeocoderStatus.OK) {
for(let i=0;i<results.length;i++){
subject.next( {label:results[i].formatted_address,value:null});
}
} else {
console.log("Geocode was not successful for the following reason: " + status);
}
});
return subj.asObservable();
}
ngOnInit(): void {
this.possiblesPlaces=this.searchTerms.filter(t=>t.length>2).debounceTime(300).distinctUntilChanged()
.flatMap(term =>
this.fetchResults(term).scan([], acc, x) => acc.concat(x));
)
.catch(error => {
// TODO: real error handling
console.log(error);
return Observable.of([]);
});
this.possiblesPlaces.map(val => console.log(val))
}
fetchResults() returns an observable. In your question you return an empty array and then only fill it later when the response from Geocoder arrives. That doesn't look too reliable.
ngOnInit() uses this.possiblePlaces.subscribe() but subscribe returns a Subscription not an Observable and | async only works with Promise and Observable but not with Subscription. If you use map instead of subscribe an Observable is returned.
Ok i figured out!
You can"t use http request like geocoder.geocode which use a callback function, you have to deal with the angular way using http.get with an url
This Meteor client code tries to make the Tracker.autorun to run once but as it appears to be that it has to run twice, once for setting and once for reactiveness.
Which is fine but it is firing 3 times. Once for setting and 2 for reacting even though the server only updated the user.profile.abc once.
To test it, I run this code in the mongodb console and the the iamge attached is what I got which confirms it fires twice.
How can I get it to run only once for responding to the changes in the users collection? Thanks
db.users.update({_id: Meteor.userId()},{$set: {'profile.ABC': ['a','b']}}).pretty()
//client
Meteor.call('cleanABC', (err) => {
if (!err) {
ABCListener();
}
});
ABCListener: () => {
Tracker.autorun(() => {
if (Meteor.userId()) {
console.log('auto run invoked');
if (Meteor.user().profile.ABC) {
const myArray = Meteor.user().profile.ABC;
//myFunction(myArray);
console.log('condition true');
} else {
console.log('condition false');
}
}
});
}
//server
'cleanABC': function() {
return Meteor.users.update({
_id: Meteor.userId()
}, {
$unset: {
'profile.ABC': ''
}
});
}
//and some where else in the code
Meteor.users.update({
_id: userId
}, {
$set: {
'profile.ABC': myArray
}
}, (err) => {
if (!err) {
console.log('just sent the array');
}
});
I think the problem is that you are just calling Tracker.autorun everytime you call the method.
I think if you change your client code to:
//client
ABCListener: () => {
Tracker.autorun(() => {
if (Meteor.userId()) {
console.log('auto run invoked');
if (Meteor.user().profile.ABC) {
const myArray = Meteor.user().profile.ABC;
//myFunction(myArray);
console.log('condition true');
} else {
console.log('condition false');
}
}
});
}
Meteor.call('cleanABC');
it should work.
I have a function loadMessages, I want it return an Observable.
loadMessages(chatId: string): Observable<Message[]> {
console.log('1');
this.autorun(() => {
const handle = this.subscribe('messages', chatId);
if (handle.ready()) {
console.log('2');
const messages = Messages.find().fetch();
return Observable.of(messages); // here return is not for this function, which is useless
}
});
console.log('3'); // I don't want this line run immediately
// I wish I can return here, but I cannot
}
How can I return to the function level?
Also, right now the order is 1 -> 3 -> 2. Is there any way to run 1 -> 2, and wait there until I get the data?
You can try something like this:
loadMessages(chatId: string): Observable<Message[]> {
console.log('1');
return Observable.create(observer => {
this.autorun(() => {
const handle = this.subscribe('messages', chatId);
if (handle.ready()) {
console.log('2');
const messages = Messages.find().fetch();
observer.next(messages)
}
});
});
}
Very simple example is here http://plnkr.co/edit/GADtB8QCTnNubtRu9SFv?p=preview