简体   繁体   中英

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();
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM