0

I am attempting to use a CanDeactivate guard to detect when a user navigates away from the component. The purpose is to #1 check if the current user owns the 'Is Being Edited' lock on the record, and #2 if so, call an observable to update the database.

The code I have is partially working, but there is a race condition where the lock isn't always released, probably because the subscription to this.updateIsBeingEdited$(false) doesn't always complete by the time the next return statement is called. So I know this isn't implemented correctly.

How can I make it such that the this.updateIsBeingEdited$ observable completes before the canDeactivate returns a value?

The return value should always be true, because the component should always deactivate, it just need to make sure this.updateIsBeingEdited$.subscribe() is done before deactivation completes.

COMPONENT

  canDeactivate(): Observable<boolean> | boolean {

    // Check if Is Being Edited must be removed
    if (this.mustReleaseIsBeingEdited()) {
      this.removeIsBeingEditedSub = this.updateIsBeingEdited$(false).subscribe(result => {
        // QUESTION: How to wait until this is complete before allowing component to deactivate?
        return true;
      }, err => { console.error(err); }, () => { });

      // Always allow deactivation
      return true;
    } else {
      return true;
    }

  }

  private updateIsBeingEdited$(isBeingEdited): Observable<boolean> {

    const editedObj = {
      _id: this.record._id,
      IsBeingEdited: isBeingEdited,
      EditedBy: this.accessLevelService.getUserId()
    }

    return this.httpService!.postData$(
      `records/_id/${editedObj._id}/IsBeingEdited/`,
      editedObj
    );

  }

GUARD

export interface ComponentCanDeactivate {
  canDeactivate: () => boolean | Observable<boolean>;
}
/**
 * Guard to notify client that user has navigated away from route
 * Implemented in component for managing record locks only
 */
@Injectable({
  providedIn: 'root'
})
export class RecordLocksGuard implements CanDeactivate<ComponentCanDeactivate> {
  canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
    if (!component) {
      return true;
    }
    component.canDeactivate();
    // Always allow component to deactivate
    return true;
  }
}
pengz
  • 2,279
  • 3
  • 48
  • 91
  • 4
    Your guard shouldn't return a boolean It should return an Observable. And your canDeactivate() method should not return a boolean either, and it should not subscribe to any Observable. It should return an Observable instead (which will be returned by the guard). – JB Nizet Nov 16 '19 at 23:19

1 Answers1

0

Thanks, here is the updated code that appears to be working.

COMPONENT

  canDeactivate(): Observable<boolean> | boolean {

    // Check if Is Being Edited must be removed
    if (this.mustReleaseIsBeingEdited()) {
      return this.updateIsBeingEdited$(false);
    } else {
      return of(true);
    }

  }

  private updateIsBeingEdited$(isBeingEdited): Observable<boolean> {

    const editedObj = {
      _id: this.record._id,
      IsBeingEdited: isBeingEdited,
      EditedBy: this.accessLevelService.getUserId()
    }

    return this.httpService!.postData$(
      `records/_id/${editedObj._id}/IsBeingEdited/`,
      editedObj
    );

  }

GUARD

export interface ComponentCanDeactivate {
  canDeactivate: () => boolean | Observable<boolean>;
}
/**
 * Guard to notify client that user has navigated away from route
 * Implemented in component for managing record locks only
 */
@Injectable({
  providedIn: 'root'
})
export class RecordLocksGuard implements CanDeactivate<ComponentCanDeactivate> {
  canDeactivate(component: ComponentCanDeactivate): boolean | Observable<boolean> {
    if (!component) {
      return of(true);
    }
    return component.canDeactivate();
  }
}
pengz
  • 2,279
  • 3
  • 48
  • 91