Home > Net >  exhaustmap in angular
exhaustmap in angular

Time:10-18

I would like to ask what is the best practice or how to use exhaust Matp to prevent multiple submissions ? like for example when user spam the SAVE button ? for example based on the sample code below , how do we handle it that it would only submit 1 at a time even the user spam or click the save button hundred times. Thanks.

#html code

 <ng-template #editButtons>
      <div class="flex" *ngIf="isEditing">
        <app-page-section-cards-btn
          [btnData]="pageSectionsOptions.btnData.cancel"
          (btnClickEvent)="cancelEdit()"></app-page-section-cards-btn>
        <app-page-section-cards-btn
          [btnData]="pageSectionsOptions.btnData.save"
          (btnClickEvent)="saveDeal()">
      </app-page-section-cards-btn>
      </div>
    </ng-template>

#ts code

 saveDeal(){
    this.isLoading = true;
    if(!this.isExistingDeal){     
      const dealTypeValues = {
        "id": 0,
        "name": this.dealPLSFormFields.dealName,
        "summary": this.dealPLSFormFields.summary,
        "mlasId": this.dealPLSFormFields.partner,
        "startDate": "estimatedOtherRevenue":this.dealPLSFormFields.estimatedOtherRevenue,
        "descriptionOfOtherRevenue":this.dealPLSFormFields.descriptionOfOtherRevenue,
        "totalMonthlyRentAndFees":this.dealPLSFormFields.totalMonthlyRentAndFees,
        "buildOutCostReimbursement":this.dealPLSFormFields.buildOutCostReimbursement,
        "dealId": 0,
        "startDateString": AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.startDate),
        "endDateString":  AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.endDate),
      } 
     
      const payload = {
        "id": 0,
        "name": this.dealPLSFormFields.dealName,
        "dealType": "Partner Location Submission",
        "annualRentProposed": null,
        "annualRentCurrent": null,
        "firmTermRemaining": null,
        "firmTermAdded": null,
        "maxAvailableTerm": null,
        "status": null,
        "capitalContribution": null,
        "parentCloneId": null,
        "accountId": this.currentAccount.accountId,
        "transactionId": this.transactionData.id,
        "dealTypeValues": JSON.stringify(dealTypeValues)
      }
      this._dealService.createDeal(payload)
      .pipe(debounceTime(500))
      .subscribe(
        res=>{
          this.isLoading = false;
          this._notificationService.showSuccess('Deal was successfully created.');
          if(res.isSuccess){
            this.refreshDealDetailsPage(res.data);
          }  
        },
        err=>{
          console.log('Error creating deal')
        }
      )
    }else{

      const dealTypeValues = {
        "id": this.dealData.dealTypeValues.id,
        "name": this.dealPLSFormFields.dealName,
        "summary": this.dealPLSFormFields.summary,
        "mlasId": this.dealPLSFormFields.partner,
        "startDate": AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.startDate),
        "endDate": AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.endDate),
        "securityMonitoringMonthly": this.dealPLSFormFields.securityMonitoringMonthly,
        "rent": this.dealPLSFormFields.rent,
        "cam": this.dealPLSFormFields.cam,
        "supportServicesFee": this.dealPLSFormFields.supportServicesFee,
        "estimatedOtherRevenue":this.dealPLSFormFields.estimatedOtherRevenue,
        "descriptionOfOtherRevenue":this.dealPLSFormFields.descriptionOfOtherRevenue,
        "totalMonthlyRentAndFees":this.dealPLSFormFields.totalMonthlyRentAndFees,
        "buildOutCostReimbursement":this.dealPLSFormFields.buildOutCostReimbursement,
        "dealId": this.dealData.dealTypeValues.dealId,
        "startDateString": AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.startDate),
        "endDateString":  AppUtils.convertDateStringToYYYYMMDD(this.dealPLSFormFields.endDate),
      } 

      const payload = {
        "id": this.dealData.id,
        "name": this.dealPLSFormFields.dealName,
        "dealType": "Partner Location Submission",
        "annualRentProposed": null,
        "annualRentCurrent": null,
        "firmTermRemaining": null,
        "firmTermAdded": null,
        "maxAvailableTerm": null,
        "status": this.dealData.status,
        "capitalContribution": null,
        "parentCloneId": null,
        "accountId": this.currentAccount.accountId,
        "transactionId": this.transactionData.id,
        "dealTypeValues": JSON.stringify(dealTypeValues)
      }
      
      this._dealService.updateDeal(payload)
      .pipe(debounceTime(500))
      .subscribe(
        res=>{
          if(res.isSuccess){
            this.isLoading = false;
            this.refreshDealDetailsPage(res.data);
          }  
        },
        err=>{
          this.isLoading = false;
          console.log('Error updating deal')
        }
      )
    }
  }
}

CodePudding user response:

There are two ways to approach this:

  1. One is to use RxJS operator debounceTime. This operator only allows 1 request per X milliseconds to pass through the observable pipe that will be called by the subscribe. So even if the user spams it only one will go through per X amount of time. This can be useful in all types of requests and not only saves.

    In your case, let's say for 500 milliseconds:

     import { debounceTime } from 'rxjs/operators';
    
     ...
    
     this._dealService.createDeal(payload)
       .pipe(debounceTime(500))
       .subscribe(
     ...
    

Edit: the exhaustMap solution suggested by Yuriy can be an even better alternative to this, since if the request is slower than 500ms then we could allow double submit with this solution

  1. The other way of approaching this is to disable the button when its clicked for the first time and until we get a response (success or error) from the backend:

     import { tap } from 'rxjs/operators';
    
     ...
    
     this._dealService.createDeal(payload)
     .pipe(
       tap(() => this.saving = true),
       finalize(() => this.saving = false),
     ).subscribe(
     ...
    

    and then you can add the disabled logic in your app-page-section-cards-btn component

     <app-page-section-cards-btn
       [disabled]="saving"
       [btnData]="pageSectionsOptions.btnData.save"
       (btnClickEvent)="saveDeal()">
    

A combination of both could be really robust but can add a lot of boilerplate code in all your components if you have this behaviour all around your app. A suggestion could be then to do the debounceTime logic in a global interceptor of the 1) part.

CodePudding user response:

There is two ways for preventing multiple submission.

  • Create variable/subject to hold info that request is pending/loading and disable a button
  • Create subject for login event.
this.loginSubject$.pipe(
    exhaustMap((loginData) => this.authService.login(loginData))
);
  • Related