Matthew
Matthew

Reputation: 3071

Angular Prevent Cancel of Form Auto Save

I have a simple Angular 11 form (in a component) that auto saves. However, the save observable is cancelled when a user navigates away from the form. Is there a way to let a user navigate away from the form but allow the save to complete (and then unsubscribe from the observable when finished)?

Idea 1: Use NgRx effects. I can't see any reason why this wouldn't technically work. But it feels like a lot of plumbing to solve this problem. Currently, the component is very simple and fully self contained.

Idea 2: Use Angular canDeactivate guard. Prompt the user if they want to lose changes. The user experience feels wrong. By the time the user clicks yes to lose changes the save observable in progress has probably already completed and saved the data.

private unsubscribe = new Subject<void>();

ngOnInit() {

    this.form = this.formBuilder.group({
        myField1: [''],
        myField2: [''],
        myField3: [''],
    });

    this.form.valueChanges.pipe(
        switchMap(formValue => this.save(formValue)),
        takeUntil(this.unsubscribe)
    ).subscribe();
}

private save(formValue: any): Observable<any> {
    return this.form.valid ? saveData(formValue) : of(null;
}

ngOnDestroy() {
    this.unsubscribe.next();
}

Upvotes: 0

Views: 292

Answers (1)

maxime1992
maxime1992

Reputation: 23803

I think this use case would indeed be handled quite well from an effect.

Dispatching the action is done synchronously so once the action is sent you don't have to worry about the observable being closed anymore when the component is destroyed.

That said, for the sake of explaining some RxJS, if you wanted to do that from your component you could do the following:

const formUpdate$ = this.form.valueChanges;

const savingForm$ = formUpdate$.pipe(
  switchMap((formValue) => this.save(formValue)),
  shareReplay({ bufferSize: 1, refCount: true })
);

const isSavingForm$ = formUpdate$.pipe(
  switchMap(() => concat(of(true), savingForm$.pipe(take(1), mapTo(false)))),
  shareReplay({ bufferSize: 1, refCount: true })
);

const unsubscribeAfterPendingFormSaved$ = combineLatest([
  this.onDestroy$,
  isSavingForm$,
]).pipe(
  switchMap(([_, isSavingForm]) => {
    if (isSavingForm) {
      return EMPTY;
    }

    return true;
  }),
  take(1)
);

savingForm$.pipe(takeUntil(unsubscribeAfterPendingFormSaved$));

Upvotes: 1

Related Questions