Reputation: 657
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.
Upvotes: 0
Views: 1695
Reputation: 4987
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();
}
Upvotes: 3