Susbcribe to many promises? - angular7

I'm new to angular and I'm facing a problem where I need to call several promises and get all their results prior to continue the process.
// Let's assume this array is already populated
objects: any[];
// DB calls
insertObject(obj1: any): Promise<any> {
return this.insertDB('/create.json', obj1);
}
updateObject(obj: any): Promise<any> {
return this.updateDB('/update.json', obj);
}
// UI invokes this:
save(): void {
this.insertObject(objects[0])
.then((result) => {
console.log(result.data[0].id);
})
.catch((reason) => {
console.debug("[insert] error", reason);
});
this.insertObject(objects[1])
.then((result) => {
console.log(result.data[0].id);
})
.catch((reason) => {
console.debug("[insert] error", reason);
});
this.updateObject(objects[1])
.then((result) => {
console.log(result.data[0].status);
})
.catch((reason) => {
console.debug("[update] error", reason);
});
//I need to catch these 3 results in order to perform the next action.
}
Any ideas on how to achieve that?

Using the syntax Promise.all(iterable), you can execute an array of Promises. This method resolves when all promises have resolved and fails if any of those promises fail.
This can help you
let firstPromise = Promise.resolve(10);
let secondPromise = Promise.resolve(5);
let thirdPromise = Promise.resolve(20);
Promise
.all([firstPromise, secondPromise, thirdPromise])
.then(values => {
console.log(values);
});

Related

Rxjs BehaviorSubject error handling when used with mergemap

I have the following code
#Injectable()
export class ReceptionService {
private generalInfoDataSrc$ = new BehaviorSubject<any>(null);
public generalInfoData = this.generalInfoDataSrc$.asObservable();
setGeneralInfo(dataSrc: GeneralInfoMModal) {
this.generalInfoDataSrc$.next(dataSrc);
}
}
From my component1 I will set the above as
OnSelect(patient: any) {
let generalInfo = new GeneralInfoMModal();
generalInfo.id = patient.id;
// some other code here
// this.recepService.setGeneralInfo(generalInfo);
}
// from component2
//
ngOnInit() { getPatientDetails() }
getPatientDetails() {
this.receptionService.generalInfoData.pipe(mergeMap(response => {
if (response && response.id) {
this.loading = true;
return this.receptionService.get('User/Get' + response.id, this.sourceobj);
} else {
return of(null);
}
}), takeUntil(this.unsubscribe$)).subscribe(response => {
this.patient = response;
this.loading = false;
}, error => {
this.loading = false;
// this.utility.showMsg('An error occurred while getting user.')
}, () => {
})
}
Every things works well. I keep on selecting a user thereby calling the User/Get api. But if in case if the api returns an error then error part is executed after which when there is a change in behaviorsubject(user is selected) it doesn't call the User/Get. Is there other way of handling errors with behaviorsubject or any other approach to handle the idea. How a behaviorsubject should be used in such a case.
If you are using the same behavior subject over and over again, and if there is an error, you need to set the behavior subject back to null, so that when the next user is set, it will get the latest value.
Try something like this:
getPatientDetails() {
this.receptionService.generalInfoData.pipe(mergeMap(response => {
if (response && response.id) {
this.loading = true;
return this.receptionService.get('User/Get' + response.id, this.sourceobj);
} else {
return of(null);
}
}), takeUntil(this.unsubscribe$)).subscribe(response => {
this.patient = response;
this.loading = false;
}, error => {
this.loading = false;
///////////////////////////////// ADD THIS LINE ///////////////////////
this.recepService.setGeneralInfo(null);
// this.utility.showMsg('An error occurred while getting user.')
}, () => {
})

Receiving returned data from firebase callable functions

I'm playing with Callable HTTPS-functions in iOS. I've created and deployed the following function:
export const generateLoginToken = functions.https.onCall((data, context) => {
const uid = data.user_id
if (!(typeof uid === 'string') || uid.length === 0) {
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "user_id" ');
}
admin.auth().createCustomToken(uid)
.then((token) => {
console.log("Did create custom token:", token)
return { text: "some_data" };
}).catch((error) => {
console.log("Error creating custom token:", error)
throw new functions.https.HttpsError('internal', 'createCustomToken(uid) has failed for some reason')
})
})
Then I call the function from my iOS-app like this:
let callParameters = ["user_id": userId]
self?.functions.httpsCallable("generateLoginToken").call(callParameters) { [weak self] (result, error) in
if let localError = self?.makeCallableFunctionError(error) {
single(SingleEvent.error(localError))
} else {
print("Result", result)
print("data", result?.data)
if let text = (result?.data as? [String: Any])?["text"] as? String {
single(SingleEvent.success(text))
} else {
let error = NSError.init(domain: "CallableFunctionError", code: 3, userInfo: ["info": "didn't find custom access token in the returned result"])
single(SingleEvent.error(error))
}
}
}
I can see on the logs that the function is invoked on the server with the right parameters, but I can't seem to the get data that is being returned from the function back into the app. It seems that the result.data value is nilfor some reason, even though I return {text: "some_data"} from the cloud function. How come?
Yikes! The issue was that I forgot to return the actual promise from the cloud function. This function is working:
export const generateLoginToken = functions.https.onCall((data, context) => {
const uid = data.user_id
if (!(typeof uid === 'string') || uid.length === 0) {
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with one argument "user_id" ');
}
return admin.auth().createCustomToken(uid)
.then((token) => {
console.log("Did create custom token:", token)
return { text: "some_data" };
}).catch((error) => {
console.log("Error creating custom token:", error)
throw new functions.https.HttpsError('internal', 'createCustomToken(uid) has failed for some reason')
})
})

Angular 2 redirect if user have shop

How i can make redirect if User -id(this.authTokenService.currentUserData.id) have shop (owner_id) .
Shop.component.ts
owner_id :number;
private sub: any;
ngOnInit() {
this.sub = this.httpService.getShops().subscribe(params => {
this.owner_id = this.authTokenService.currentUserData.id ;
console.log(this.sub)
console.log(this.owner_id)
});
if (this.sub) {
this.router.navigate(['/profile']);
}
Http.service.ts
getShops(){
return this.http.get('http://localhost:3000/shops.json')
}
I use Rails 5 for api and auth token. Thanks for help. Sorry for my English
I can't complete grasp your intentions here, but this code seems to be what you're after. Hope this helps
ngOnInit() {
this.getUserShops(this.authTokenService.currentUserData.id)
.subscribe((ownerShops) => {
if (ownerShops.length > 0) {
// User has at least 1 shop
this.router.navigate(['/profile']);
} else {
// User has no shops
}
})
}
getUserShops(ownerId: number): Observable<any> {
return this.httpService
.getShops()
// Map the returned array to a filtered subset including only the owners id
.map((shops: any[]) => shops.filter(shop => shop.owner_id === ownerId));
}
// http.service.ts
export class HttpService {
getShops(): Observable<Shop[]> {
return this.http.get('http://localhost:3000/shops.json')
.map((res: Response) => res.json())
}
}
export interface Shop {
owner_id: number | null;
}
EDIT: Added update to show example http.service.ts typings
private sub: any;
ngOnInit() {
this.httpService.getShops()
.subscribe(params => {
// code here is executed when the response arrives from the server
this.owner_id = this.authTokenService.currentUserData.id ;
console.log(this.sub)
console.log(this.owner_id)
if (this.sub) {
this.router.navigate(['/profile']);
}
});
// code here is executed immediately after the request is sent
// to the server, but before the response arrives.
}
If the code depends on sub that you get from subscribe, then you need to move the code inside the subscribe callback, otherwise it will be executed before this.sub gets a value.
try this code.
owner_id :number;
private sub: any;
ngOnInit() {
this.httpService.getShops().subscribe(
response => {
this.sub = response
if (this.sub.length>0) {
this.router.navigate(['/profile']);
}
}
});

Pass over throwned error with completeError

I try to write an application that insert record into mongodb.
First look at my test:
test('Password test failed, not strong enough.', () {
Account.create({'name': 'eric', 'email': 'koston#mail.com', 'password': 'Test'})
.catchError((err) {
expect(err, throwsA(new isInstanceOf<DuplicateError>()));
});
});
This test should be failed, because the password is not strong enough. And the code, that try to insert record.
static Future<String> create(Map account) {
var completer = new Completer();
String hashed_password;
var self = new Account();
if(self._signUpKeys.length != account.length) {
return completer.completeError(new LengthError(I18n.instance.getTextByMap('TEXT1')));
}
for(var singUpKey in self._signUpKeys) {
if (!account.containsKey(singUpKey)) {
return completer.completeError(new ArgumentError(I18n.instance.getTextByMap('TEXT1')));
}
}
// Try to find duplication
Future.wait([findAccountByField('name', account['name']),
findAccountByField('email', account['email'])])
.then((Iterable<Map<String, String>> lists) {
// Check of any duplications
lists.forEach((value){
value.forEach((String key, String value) {
switch(key) {
case('name'):
return completer.completeError(new DuplicateError(
I18n.instance.getTextWithMarker('TEXT2', {'&1': value})));
case('email'):
return completer.completeError(new DuplicateError(
I18n.instance.getTextWithMarker('TEXT3', {'&1': value})));
}
});
hashed_password = Account.generateHashPassword(account['password']);
self._insert(self._fillDbFields(name: account['name'], email: account['email'], hashed_password: hashed_password,
created_at: new DateTime.now(), activated: false))
.then((result) => completer.complete(result));
});
})
.catchError((err) {
completer.completeError(err);
});
return completer.future;
}
this allocation will thrown an error, because the password is not according to security requirement.
hashed_password = Account.generateHashPassword(account['password']);
and this part should catch error and complete it.
.catchError((err) {
completer.completeError(err);
});
but in the test, I've got NoSuchMethodError. Why here, the error object is not pass over to test? What i do here wrong?
I think you should check what value lists has here
Future.wait([findAccountByField('name', account['name']),
findAccountByField('email', account['email'])])
.then((Iterable<Map<String, String>> lists) {
if it is null you can't call forEach on it

Get one database record, display it, update it, and save it back to the database

SOLVED! It was a Knockout issue (wrong binding). But maybe someone likes to argue or comment about the code in general (dataservice, viewmodel, etc).
I tried to build a Breeze sample, where I get one database record (with fetchEntityByKey), display it for updating, then with a save button, write the changes back to the database. I could not figure out how to get it to work.
I was trying to have a dataservice ('class') and a viewmodel ('class'), binding the viewmodel with Knockout to the view.
I very much appreciated if someone could provide a sample or provide some hints.
Thankx, Harry
var dataservice = (function () {
var serviceName = "/api/amms/";
breeze.NamingConvention.camelCase.setAsDefault();
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName,
entityManager: entityManager,
init: init,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function init() {
return getMetadataStore();
}
function getMetadataStore() {
return entityManager.fetchMetadata()
.then(function (result) { return dataservice; })
.fail(function () { window.alert("fetchMetadata:fail"); })
.fin(function () { });
}
function saveChanges() {
return entityManager.saveChanges()
.then(function (result) { return result; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () { window.alert("fetchEntityByKey:fail"); })
.fin(function () { });
}
})();
var viewmodel = (function () {
var viewmodel = {
location: null,
error: ko.observable(""),
init: init,
saveChanges: null
};
return viewmodel;
function init() {
return dataservice.init().then(function () {
viewmodel.saveChanges = dataservice.saveChanges;
return getLocation();
})
}
function getLocation() {
return dataservice.getLocation().then(function (result) {
return viewmodel.location = result;
})
}
})();
viewmodel.init().then(function () {
ko.applyBindings(viewmodel);
});
Glad you solved it. Can't help noticing that you added a great number of do-nothing callbacks. I can't think of a reason to do that. You also asked for metadata explicitly. But your call to fetchEntityByKey will do that implicitly for you because, as you called it, it will always go to the server.
Also, it is a good idea to re-throw the error in the fail callback within a dataservice so that a caller (e.g., the ViewModel) can add its own fail handler. Without re-throw, the caller's fail callback would not hear it (Q promise machinery acts as if the first fail handler "solved" the problem).
Therefore, your dataservice could be reduced to:
var dataservice = (function () {
breeze.NamingConvention.camelCase.setAsDefault();
var serviceName = "/api/amms/";
var entityManager = new breeze.EntityManager(serviceName);
var dataservice = {
serviceName: serviceName, // why are you exporting this?
entityManager: entityManager,
saveChanges: saveChanges,
getLocation: getLocation
};
return dataservice;
function saveChanges() {
return entityManager.saveChanges()
.fail(function () {
window.alert("saveChanges failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
function getLocation() {
return entityManager.fetchEntityByKey("LgtLocation", 1001, false)
.then(function (result) { return result.entity; })
.fail(function () {
window.alert("fetchEntityByKey failed: " + error.message);
throw error; // re-throw so caller can hear it
})
}
})();
I don't want to make too much of this. Maybe you're giving us the stripped down version of something more substantial. But, in case you (or a reader) think those methods are always necessary, I wanted to make clear that they are not.

Resources