Chained Promis in angularjs - javascript

I am new to angular and I am struggling to see how I should create a particular promise. I am also using typescript for coding.
I need to call a web service to authenticate this has to be done in 2 steps.
Step 1
Request an authentication key
Step 2
Process logon details with authentication key and return to service to retrieve the logged on user or error if incorrect
So I have created an angular service called AuthenticationService
like below (this of course doesn't work)
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
$http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
var promiseToReturn = $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
How do I go about returning a promise with the correct return type from the authentication method that returns the second result?

I can tell you that in JavaScript as i am not conversant with typescript. The idea is to create your own promise and resolve it whenever you want. Basically
var autenticate=function(user,password) {
var defer=$q.defer();
$http.post( "/Account/AuthenticationKey", null )
.then(function(data) {
//do something on data
$http.post( "/Account/Authenticate", { key: strKey })
.then(function(result) {
defer.resolve(result)
});
})
return defer.promise;
}

A then function should always either return another promise or a return value. The final then function should return a value which will get propagated to the top.
The relevant documentation can be found here :
https://github.com/kriskowal/q
NOTE : Angular's promise implementation is based on kriskowal's q.
This is the relevant section of the documentation :
If promiseMeSomething returns a promise that gets fulfilled later with
a return value, the first function (the fulfillment handler) will be
called with the value. However, if the promiseMeSomething function
gets rejected later by a thrown exception, the second function (the
rejection handler) will be called with the exception.
In your case, you should do something like
export class Authentication
{
$inject: string[] = ['$http'];
public Authenticate( userName: string, password: string ): ng.IHttpPromise<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>
{
return $http.post( "/Account/AuthenticationKey", null )
.then<ApiModel.ApiResult<string>>( result =>
{
var strKey = userName + password; //TODO: Put correct code here!
return $http.post( "/Account/Authenticate", { key: strKey })
.then<ApiModel.ApiResult<ApiModel.AuthenticatedUser>>( result =>
{
return result;
});
});
}
}
Notice the two returns before the $http.posts that you are calling. All $http methods in Angular return a promise, which means you dont need to explicitly create another promise.

Related

Use data from one observable in another and then return result as other observable

I am solving this issue:
The application flow:
I have to call the first API endpoint (let's call it EP-A for simplicity) which takes Blob as body and fileType as a request parameter. Its performed via calling automatically generated class
uploadFile$Response(params?: {
fileType?: 'USER_AVATAR' | 'UNKNOWN' | 'DELIVERY_LOGO' | 'PAYMENT_LOGO' | 'ITEM_PICTURE';
body?: { 'file'?: Blob }
}): Observable<StrictHttpResponse<FileUploadResponse>> {
const rb = new RequestBuilder(this.rootUrl, FileControllerService.UploadFilePath, 'post');
if (params) {
rb.query('fileType', params.fileType, {});
rb.body(params.body, 'application/json');
}
return this.http.request(rb.build({
responseType: 'blob',
accept: '*/*'
})).pipe(
filter((r: any) => r instanceof HttpResponse),
map((r: HttpResponse<any>) => {
return r as StrictHttpResponse<FileUploadResponse>;
})
);
}
The StrictHttpResponse<T> is simply an interface holding a "generic" body (so you can retrieve data that will have a structure defined by swagger from which this method is generated).
Then the result FileUploadResponse which is an object like
{
uuid: string,
time: Timestamp
...
Other members omitted for simplicity
...
}
is sent to another EP (let's call it EP-B) right after EP-A call returns a value, EP-B takes an object below as a body and currently logged person as a path variable.
{
uuid: string
}
So before calling EP-B the result from EP-A should be parsed (in this case, the uuid field should be taken and put into a new object for EP-B calling)
Again via the generated method with a similar signature as the one above (and I will omit it for simplicity).
If everything performed well, I´d like to let the caller know about that. If anything failed (any of these 2 EP calls), I´d like to let it know to call of this method to react somehow (show alert, change page somehow, ...)
The method I have is now incomplete, I do not know how to "connect" these 2 Observables, I´ve read about mergeMap, flatMap, etc. but I am not sure how to use it in my case.
updateUserAvatar(avatar: Blob): Observable<boolean> {
return new Observable<boolean>((observer) => {
// Calling EP-A
this.avatarFormChangeRequestSubscription = this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar
}
})
.subscribe((response: StrictHttpResponse<FileUploadResponse>) => {
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
},
(error: any) => {
observer.error(error);
});
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '' // the UUID from observable response above should be placed here
}
};
// Calling EP-B
this.avatarFormChangeRequestSubscription = this.userControllerService.updateUserAvatar$Response(avatarUpdateParams)
.subscribe((response: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
},
(error: any) => {
observer.error(error);
});
});
}
At the end I would like to add "use case" flow too to understand what I am trying to achieve from user view:
User uploads his photo which is firstly uploaded into a file system (and linked with database record) on BE side, then this file is linked to his profile as his profile picture.
You could do it using rxjs. Something like that might works :
this.fileControllerService.uploadFile$Response({
fileType: 'USER_AVATAR',
body: {
file: avatar,
},
})
.pipe(
tap((responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Do whatever you want here, but you might not need that since you get the response below as well (in the flatMap)
// Handle returned UUID and somehow pass it into an observable belog
console.log(response);
}),
flatMap(
(responseOfFirstApiCall: StrictHttpResponse<FileUploadResponse>) => {
// Creating object for EP-B calling
const avatarUpdateParams = {
id: 1, // Just dummy ID for now, will be dynamically changed
body: {
avatarUUID: '', // the UUID from observable response above should be placed here
},
};
return this.userControllerService.updateUserAvatar$Response(avatarUpdateParams);
}
),
tap((responseOfTheSecondApiCall: StrictHttpResponse<string>) => {
// Handle successfull avatar upload (change the "Logged user" object avatar to change it everywhere etc
console.log(response);
observer.next(true);
}),
catchError((err: any) => of(err))
)
.subscribe(); // Empty subscribe() call to trigger the http request. Not needed if you get the result somewhere else (eg if your method return an observable that you want to handle the result somewhere else)
flatMap() is the same as mergeMap. Change it as you wish, there's a lot of option like map or switchMap that you should learn about since they are useful.
Basically, the pipe allow you to chain functions, and if there is an error, then the catchError is triggered.
Tip: Note that what is in the pipe is executed BEFORE the result of your api call. So if you want to do something with your result before to get it, then think about rxjs:
service
getUser(id: string) {
return this._http.get<any>(url).pipe(
map(result => result.email), // Return only the email
);
}
component:
ngUnsubscribe = new Subject();
ngOnInit() {
this._userService.getUser(1)
.pipe(takeUntil(this.ngUnsubscribe)) // Don't forget to unsubscribe !
.subscribe(email => console.log('email = ', email))
}
ngOnDestroy() {
this.ngUnsubscribe.unsubscribe();
// or
// this.ngUnsubscribe.next();
// this.ngUnsubscribe.complete();
}

Extending `Promise` and change `then` signature

I want to extend Promise and change the then signature so its callback receives two values. I tried different approaches two of which are documented and tested here. Sadly, I get various errors or the resulting class does not behave like a Promise.
Approach 1: Wrapping a native Promise
export class MyWrappedPromise {
constructor(data) {
this.data = data;
this.promise = new Promise(evaluate.bind(data));
}
then(callback) {
this.promise.then(() => callback(this.data, ADDITIONAL_DATA));
}
catch(callback) {
this.promise.catch(callback);
}
}
Approach 2: Extending native Promises
export class MyExtendedPromise extends Promise {
constructor(executor, data) {
super(executor);
this.data = data;
}
static create(data) {
return new MyExtendedPromise(evaluate.bind(data), data);
}
then(callback) {
return super.then(() => callback(this.data, ADDITIONAL_DATA));
}
}
Does anyone have any suggestion on what I am doing wrong? Feel free to create a PR on GitHub.
thanks
------------------- Edit ---------------------
Some Additional code and info to make the code above more understandable without looking at the code and tests on Github.
evaluate is just the Promise executor function. I extracted it out so I can keep it consistent across all my implementations and tests. It may look convoluted but it's structured that way to simulate my "real" project.
export function evaluate(resolve, reject) {
const data = this;
function getPromise(data) {
return !!data ? Promise.resolve(data) : Promise.reject(new Error("Error"));
}
getPromise(data)
.then(resolve)
.catch(reject);
}
ADDITIONAL_DATA is just a string to simulate the second value in the callback. It's also extracted to be consistent across all versions and tests.
------------------- Edit 2---------------------
Errors that come up depending on the solution
catch is not accessible
A lot of UnhandledPromiseRejectionWarning: warnings because errors/rejects are not getting propagated up correctly.
Errors/rejects are getting thrown too early and don't even reach the rejects checks in my test suites
You have problems (especially with unhandled rejections) because you are not implementing the then interface correctly. Remember that .catch(onRejected) is just an alias for .then(undefined, onRejected), and then with two parameters is the actual core method of every promise.
You were always ignoring the second argument, so no rejection ever got handled. You need to write
then(onFulfilled, onRejected) {
return super.then(res => onFulfilled(res, this.ADDITIONAL_DATA), onRejected);
// or `this.promise.then` instead of `super.then`
}
I don't understand very well why you do have a factory method, instead of using directly the constructor.
Do you mean something like this?
class MyExtendedPromise extends Promise {
constructor(executor, data) {
super(executor);
this.data = data;
}
then(callback, test) {
console.log('passed new parameter in then:', test);
console.log('additional data:', this.data);
return super.then(data => callback(data, test));
}
}
new MyExtendedPromise((resolve, reject) => {
setTimeout(() => resolve(true), 2000);
}, 'other additional data').then(data => console.log('my then', data), 'hello world');

Angular : How to handle the async set of a variable

Under my Angular 6 app , i have this service ; where i'm declaring a varibale called permittedPefs , this variable is setted asychronsouly within a httpClient.get call.
#Injectable()
export class myService implements OnInit {
permittedPefs = [];
constructor(){}
ngOnInit() {
// STEP 1
this.loadUserPefsService.getUserRolePefs(roleId).subscribe(
(returnedListPefs) => {
this.permittedPefs = returnedListPefs;
},
error => {
console.log(error);
});
}
// STEP 2
this.myMethod(1);
After that , i ve a call of this method which is using my -supposed setted - var
myMethod(pefId): boolean {
return this.permittedPefs.includes(pefId);
}
the problem is it seems that permittedPefs , haven't got its value yet , and the call of myMethod() point to a wrong value of "permittedPefs"
So what's the simpliest way to make it wait to the just after the http response , without calling if from the http Response callback (as i 'm using it in several places)
Sugesstions??
Asynchronous Hell ! the best choice here is to get an Observable instead of a value
in your service :
getValue (): Observable<any>{
return this.loadUserPefsService.getUserRolePefs(roleId);
}
in your method :
myMethod(pefId): boolean {
this.yourservice.getValue().subscribe(
data => {
if(data){
return data.includes(pefId);
}
});
}
This happens because your method is called when you have not received the result yet. so just move the function call in subscribe function
Call the method this.myMethod(1); from subscription block, so that you wait for asynchronous call to be completed, which will then set the value of permittedPefs.
ngOnInit() {
// STEP 1
this.loadUserPefsService.getUserRolePefs(roleId).subscribe(
(returnedListPefs) => {
this.permittedPefs = returnedListPefs;
// STEP 2
this.myMethod(1); // check this line
},
error => {
console.log(error);
});
}

Angular 5 switch to HttpClient broke my services

Before Angular 5, we imported and used Http from #angular/http. Now we use HttpClient from #angular/common/http. There's some level of convenience added, because no we don't need the unnecessary step of turning all of our response data to JSON format every time we make a call using .map(res => res.json()). However, now we aren't suppose to use .map. We are suppose to go straight to .subscribe(). Again, this is suppose to be convenient. However, I used to do some logic in my services, where my API calls live, before returning the data to the subscribing functions in other components. For example,
The component:
getData(id) {
this.service.getDataFromApi(id).subscribe(res => this.doSomething(res));
}
The service:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response.json();
return this.data;
});
}
}
Now, they suggest we shorten the service call to something like the following:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id);
}
}
I am not suppose to use .map because it will no longer be supported in the future. How can I do some logic before returning the data? I don't want to make a call for some data that already exists and doesn't change. Multiple components are using this service. Am I suppose to turn every single service call into a raw promise and run some logic before resolving? If I were to just call subscribe right on the component's function, I wouldn't even need to have the service. Am I missing something here?
You can use map. The new HttpClient has the added convenience of defaulting the response type to JSON, but it still returns an Observable -- and there are no plans to deprecate map from the Observable API.
So, your code only needs slight modification (take out the .json()):
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response;
return this.data;
});
}
}
See the Observable API docs here.
If you use the new HttpClient, there is no res.json method. It will work automatically, just pass the response type like this:
return this.http.get(this.apiPath + id)
.toPromise()
.then(data => {
console.log(data)
return data
});

Set global variable of class from inside a promise Angular 2

I am facing a weird issue in assigning response to a class's global variable from inside a observable. So my program logic is as follows:
Get latest playlists ID's from elastic search (i use elastic search from a type definition file). This returns me a PromiseLike to which i hook a then operator.
Inside the promise resolution, i make another http get call (i.e an observable)
In Observable subscription, i assign my global array with the response from the server.
Code is working correctly, I am getting responses as they should be but i cant assign the variable to the global one.
Here is my code:
import {Component, OnInit} from '#angular/core';
import {PlaylistService} from '../api/services'
#Component({
selector: 'app-playlists',
templateUrl: './playlists.component.html',
styleUrls: ['./playlists.component.css']
})
export class PlaylistsComponent implements OnInit {
public playlists: any[] = [];
constructor(private playlistService: PlaylistService) {
}
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
});
}
}
The listIds function is as follows:
listIds() {
return this.api.listing('playlist').then((body) => {
let hits = body.hits.hits;
return _.keys(_.groupBy(hits, '_id'));
});
}
and here is my api.listing function (elastic search client)
listing(type: string) {
let es = this.prepareES();
return es.search({
index: 'test',
_source: ["_id"],
type: type
});
}
The return type of es.search is
search(params: SearchParams): PromiseLike>;
Any ideas why i am not being able to assign value to global variable?
It looks like the promise returned by this.playlistservice.listIds() doesn't run inside Angulars zone. This is why Angular2 doesn't run change detection and doesn't recognize the change.
You can invoke change detection explicitly after the change:
constructor(private playlistService: PlaylistService, private cdRef:ChangeDetectorRef) {
...
ngOnInit() {
let that = this;
this.playlistService.listIds().then((val) => { // <-- promise resolution
return this.playlistService.getByIds(val).toPromise(); // <-- http get call which i then convert to promise for simplicity
}).then((res) => { // <-- resolution of the http get call
console.log(this.playlists); <-- in this log, i get my desired results
// here is my problem, this assignment doesn't happens
this.playlists = res.data;
this.cdRef.detectChanges();
});
}
Can you try passing
this.playlistService.listIds()
call inside your
return this.playlistService.getByIds(val)
replace val with first service call and see if your view gets updated. Just for testing purpose like
return this.playlistService.getByIds(this.playlistService.listIds())
.then((results)=>{/*rest of logic here*/});

Categories